Rust testing

From wikinotes

Documentation

tests intro https://doc.rust-lang.org/stable/book/ch11-01-writing-tests.html
doc-comment tests https://doc.rust-lang.org/stable/book/ch14-02-publishing-to-crates-io.html#documentation-comments-as-tests

Running Tests

By default, tests run in parallel

cargo test                                  # run all tests (unit/integration/doc)
cargo test tests::add_two_positive_numbers  # run specific test
cargo test add                              # run all tests with the word 'add' in their name
cargo test --test my_integration_test       # run all tests in 'tests/my_integration_tests'

cargo test -- --show-output                 # show printed output (in both passing/failed)
cargo test -- --ignored                     # only run ignored tests

Test Types

Unit Tests

In rust, it's customary to define your tests in the same file as your code.

Define your tests

// src/lib.rs

// real code
fn add<T>(a: T, b: T) -> T {
    a + b
}

// test code
#[cfg(test)]       // tell rust not to compile unless testing
mod tests {
    use super::*;  // include parent module (our src!)

    #[test]
    fn add_two_positive_numbers() {
        let res = add(1, 2);
        assert_eq!(res, 3);
    }

    #[test]
    fn add_a_postive_and_negative_number() {
        let res = add(1, -2);
        assert_eq!(res, -1);
    }
}
cargo test  # run tests

Integration Tests

Basics

Each integration test is it's own crate.
They are written in the tests/ directory.

NOTE:

executable crates can't be imported, so they can't have integration tests.
the convention is to keep your main.rs very simple, and test your executable's lib.rs crate.

// tests/add_integration_tests.rs

use my_code::*;

#[test]
fn add_two_positive_numbers() {
    assert_eq!(add(1, 3), 4);
}

Setup/Teardown/Testlib

Setup/Teardown code is just regular functions.
You must add them using a some_module/mod.rs so that it is not run as tests.

my_project/
  tests/
    common/
      mod.rs                   // <-- test lib code here
    add_integration_tests.rs
// tests/common/mod.rs

pub fn setup() {
    // ...
}
// tests/add_integration_tests.rs
mod common;

#[test]
fn do_the_thing() {
    common::setup();
}

DocComment Tests

TODO

Assertions

Some common assertions.

NOTE:

rust doesn't care what order assertion params are defined in.
there is not a convention for one side with expectaton and the other for actual.

assert_eq!(result, expects);            // assert equal
assert_ne!(result, expects);            // assert not equal
assert_eq!(result, "reason", expects);  // add a message to assertion

assert!(result)                         // assert true
assert!(!result)                        // assert false

You can assert that code panics by assigning an attribute.

// assert any part of code panics
#[test]
#[should_panic]
fn open_non_existant_file_should_panic() {
    // ...
}

// assert panics with message
#[test]
#[should_panic(expected = "foo is not a number")]
fn open_non_existant_file_should_panic() {
    // ...
}

Test functions can also use the Result type.

#[test]
fn open_non_existant_file_should_panic() -> Result<(), String> {
    if add(1, 3) == 4 {
        Ok(())
    } else {
        Err("1 plus 3 should be 4".to_string())
    }
}

Ignore Tests

skip this test

#[test]
#[ignore]
fn open_non_existant_file_should_panic() {
    // ...
}