Writing rust automated tests

// add new module with test specific annotations
#[cfg(test)]
mod tests {
    #[test]
    fn exploration() {
        assert_eq!(2 + 2, 4);
    }
}
# running test with the following:
cargo test
#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}
 
impl Rectangle {
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}
 
#[cfg(test)]
mod tests {
    use super::*;
 
    #[test]
    fn larger_can_hold_smaller() {
        let larger = Rectangle {
            width: 8,
            height: 7,
        };
        let smaller = Rectangle {
            width: 5,
            height: 1,
        };
 
        // checking results with the `assert!` macro
        assert!(larger.can_hold(&smaller));
    }
}
pub fn greeting(name: &str) -> String {
    String::from("Hello!")
}
 
#[cfg(test)]
mod tests {
    use super::*;
 
    #[test]
    fn greeting_contains_name() {
        let result = greeting("Carol");
        // adding custom failure messages
        assert!(
            result.contains("Carol"),
            "Greeting did not contain name, value was `{}`",
            result
        );
    }
}
pub struct Guess {
    value: i32,
}
 
impl Guess {
    pub fn new(value: i32) -> Guess {
        if value < 1 {
            panic!(
                "Guess value must be greater than or equal to 1, got {}.",
                value
            );
        } else if value > 100 {
            panic!(
                "Guess value must be less than or equal to 100, got {}.",
                value
            );
        }
 
        Guess { value }
    }
}
 
#[cfg(test)]
mod tests {
    use super::*;
 
    // checking panics with `should_panic`
    #[test]
    #[should_panic(expected = "Guess value must be less than or equal to 100")]
    fn greater_than_100() {
        Guess::new(200);
    }
}
#[cfg(test)]
mod tests {
    // using Result<T, E> in Tests
    #[test]
    fn it_works() -> Result<(), String> {
        if 2 + 2 == 4 {
            Ok(())
        } else {
            Err(String::from("two plus two does not equal four"))
        }
    }
}

Controlling how tests are run

# no parallelism test execution
cargo test -- --test-threads=1
 
# display printed values for passing tests as well
cargo test -- --show-output

Using the following Rust code:

pub fn add_two(a: i32) -> i32 {
    a + 2
}
 
#[cfg(test)]
mod tests {
    use super::*;
 
    #[test]
    fn add_two_and_two() {
        assert_eq!(4, add_two(2));
    }
 
    #[test]
    fn add_three_and_two() {
        assert_eq!(5, add_two(3));
    }
 
    #[test]
    fn one_hundred() {
        assert_eq!(102, add_two(100));
    }
}
# running a single test using the test method name
cargo test one_hundred
# running multiple tests by specifying part of a test name
cargo test add
#[test]
fn it_works() {
    assert_eq!(2 + 2, 4);
}
 
// ignoring test with ignore annotation
#[test]
#[ignore]
fn expensive_test() {
    // code that takes an hour to run
}

Test organization

Unit tests

The purpose of unit tests is to test each unit of code in isolation from the rest of the code to quickly pinpoint where code is and isn’t working as expected. You’ll put unit tests in the src directory in each file with the code that they’re testing. The convention is to create a module named tests in each file to contain the test functions and to annotate the module with cfg(test).

The #[cfg(test)] annotation on the tests module tells Rust to compile and run the test code only when you run cargo test, not when you run cargo build. This saves compile time when you only want to build the library and saves space in the resulting compiled artifact because the tests are not included.

Testing private functions

pub fn add_two(a: i32) -> i32 {
    internal_adder(a, 2)
}
 
fn internal_adder(a: i32, b: i32) -> i32 {
    a + b
}
 
#[cfg(test)]
mod tests {
    use super::*;
 
    #[test]
    fn internal() {
        // possible to test private functions
        assert_eq!(4, internal_adder(2, 2));
    }
}

Integration tests

Integration tests are in another directory:

$ tree
├── src
│   └── lib.rs
└── tests
    └── integration_test.rs

tests/integration_test.rs:

use adder;
 
#[test]
fn it_adds_two() {
    assert_eq!(4, adder::add_two(2));
}
# run all the tests in a particular integration test file:
cargo test --test integration_test

Submodules in integration tests

$ tree
├── src
│   └── lib.rs
└── tests
    ├── common
    │   └── mod.rs
    └── integration_test.rs
// tests/common/mod.rs
pub fn setup() {
    // setup code specific to your library's tests would go here
}
 
// tests/integration_test.rs
use adder;
 
mod common;
 
#[test]
fn it_adds_two() {
    common::setup();
    assert_eq!(4, adder::add_two(2));
}