Tests

This chapter gives a small example of testing in Rust by comparison to Boost.Test for C++. A more thorough guide to testing in Rust is available as part of the Rust Book, including information on documentation testing. Details on the built-in support for testing in rustc and Cargo are available in their respective manuals.

Unit tests

When using C++ test frameworks such as Boost.Test, tests are defined as functions which are registered with the framework. The tests are compiled against a test driver provided by the framework.

In Rust, testing is handled similarly. Unlike with C++, the test registration mechanism and test driver are both provided by Rust itself.

Additionally, the way that tests are organized is different. In C++, unit tests are defined in separate files from the units under test. In Rust, they are typically defined in the same file, usually in a test sub-module, with inclusion of the module controlled by the test feature flag via the #[cfg(test)] annotation.

The example below defines a small class and tests for that class. In C++ this involves creating three separate files: a header file for the interface, the implementation of the interface, and the test driver. In Rust, this is all done in a single file.

// counter.h
#ifndef COUNTER_H
#define COUNTER_H

class Counter {
  unsigned int count;

public:
  Counter();
  unsigned int get();
  void increment();
};

#endif

// counter.cc
#include "counter.h"
Counter::Counter() : count(0) {}

unsigned int Counter::get() {
  return count;
}

void Counter::increment() {
  ++count;
}

// test_main.cc
#define BOOST_TEST_MODULE my_tests
#include <boost/test/included/unit_test.hpp>
#include "counter.h"

BOOST_AUTO_TEST_CASE(test_counter_initialize) {
  Counter c;
  BOOST_TEST(c.get() == 0);
}

BOOST_AUTO_TEST_CASE(test_counter_increment) {
  Counter c;
  c.increment();
  BOOST_TEST(c.get() == 1);
}
#![allow(unused)]
fn main() {
// counter.rs
pub struct Counter(u32);

impl Counter {
    pub fn new() -> Counter {
        Counter(0)
    }

    pub fn get(&self) -> u32 {
        self.0
    }

    pub fn increment(&mut self) {
        self.0 += 1;
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn counter_initialize() {
        let c = Counter::new();
        assert_eq!(0, c.get());
    }

    #[test]
    fn counter_increment() {
        let mut c = Counter::new();
        c.increment();
        assert_eq!(1, c.get());
    }
}
}

Defining unit tests within the module being tested has the benefit of making internals of the module under test visible to the test code. This makes it possible to unit test internal components without exposing them to the rest of the program, as one would have to in C++ by including declarations in the header file and by declaring test fixtures as friends.

Running the tests in C++ involves linking against boost_unit_test_framework. In Rust, the tests can be run with cargo test. If not using Cargo, the tests can be compiled by passing the flag --test to rustc, and running the executable produced.

Integration tests

Integration tests as supported by Cargo, are still defined outside of the module and outside of the crate, purely in terms of the exposed API.

See the Rust Book for details on how to organize integration tests for Rust programs.