Unit testing

Tests are Rust functions that verify that the non-test code is functioning in
the expected manner. The bodies of test functions typically perform some setup,
run the code we want to test, then assert whether the results are what we
expect.

Most unit tests go into a tests mod with the #[cfg(test)] attribute.
Test functions are marked with the #[test] attribute.

Tests fail when something in the test function panics. There are some
helper macros:

  • assert!(expression) - panics if expression evaluates to false.
  • assert_eq!(left, right) and assert_ne!(left, right) - testing left and
    right expressions for equality and inequality respectively.
  1. pub fn add(a: i32, b: i32) -> i32 {
  2. a + b
  3. }
  4. // This is a really bad adding function, its purpose is to fail in this
  5. // example.
  6. #[allow(dead_code)]
  7. fn bad_add(a: i32, b: i32) -> i32 {
  8. a - b
  9. }
  10. #[cfg(test)]
  11. mod tests {
  12. // Note this useful idiom: importing names from outer (for mod tests) scope.
  13. use super::*;
  14. #[test]
  15. fn test_add() {
  16. assert_eq!(add(1, 2), 3);
  17. }
  18. #[test]
  19. fn test_bad_add() {
  20. // This assert would fire and test will fail.
  21. // Please note, that private functions can be tested too!
  22. assert_eq!(bad_add(1, 2), 3);
  23. }
  24. }

Tests can be run with cargo test.

  1. $ cargo test
  2. running 2 tests
  3. test tests::test_bad_add ... FAILED
  4. test tests::test_add ... ok
  5. failures:
  6. ---- tests::test_bad_add stdout ----
  7. thread 'tests::test_bad_add' panicked at 'assertion failed: `(left == right)`
  8. left: `-1`,
  9. right: `3`', src/lib.rs:21:8
  10. note: Run with `RUST_BACKTRACE=1` for a backtrace.
  11. failures:
  12. tests::test_bad_add
  13. test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out

Testing panics

To check functions that should panic under certain circumstances, use attribute
#[should_panic]. This attribute accepts optional parameter expected = with
the text of the panic message. If your function can panic in multiple ways, it helps
make sure your test is testing the correct panic.

  1. pub fn divide_non_zero_result(a: u32, b: u32) -> u32 {
  2. if b == 0 {
  3. panic!("Divide-by-zero error");
  4. } else if a < b {
  5. panic!("Divide result is zero");
  6. }
  7. a / b
  8. }
  9. #[cfg(test)]
  10. mod tests {
  11. use super::*;
  12. #[test]
  13. fn test_divide() {
  14. assert_eq!(divide_non_zero_result(10, 2), 5);
  15. }
  16. #[test]
  17. #[should_panic]
  18. fn test_any_panic() {
  19. divide_non_zero_result(1, 0);
  20. }
  21. #[test]
  22. #[should_panic(expected = "Divide result is zero")]
  23. fn test_specific_panic() {
  24. divide_non_zero_result(1, 10);
  25. }
  26. }

Running these tests gives us:

  1. $ cargo test
  2. running 3 tests
  3. test tests::test_any_panic ... ok
  4. test tests::test_divide ... ok
  5. test tests::test_specific_panic ... ok
  6. test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
  7. Doc-tests tmp-test-should-panic
  8. running 0 tests
  9. test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

Running specific tests

To run specific tests one may specify the test name to cargo test command.

  1. $ cargo test test_any_panic
  2. running 1 test
  3. test tests::test_any_panic ... ok
  4. test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out
  5. Doc-tests tmp-test-should-panic
  6. running 0 tests
  7. test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

To run multiple tests one may specify part of a test name that matches all the
tests that should be run.

  1. $ cargo test panic
  2. running 2 tests
  3. test tests::test_any_panic ... ok
  4. test tests::test_specific_panic ... ok
  5. test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out
  6. Doc-tests tmp-test-should-panic
  7. running 0 tests
  8. test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

Ignoring tests

Tests can be marked with the#[ignore] attribute to exclude some tests. Or to run
them with command cargo test -- --ignored

  1. pub fn add(a: i32, b: i32) -> i32 {
  2. a + b
  3. }
  4. #[cfg(test)]
  5. mod tests {
  6. use super::*;
  7. #[test]
  8. fn test_add() {
  9. assert_eq!(add(2, 2), 4);
  10. }
  11. #[test]
  12. fn test_add_hundred() {
  13. assert_eq!(add(100, 2), 102);
  14. assert_eq!(add(2, 100), 102);
  15. }
  16. #[test]
  17. #[ignore]
  18. fn ignored_test() {
  19. assert_eq!(add(0, 0), 0);
  20. }
  21. }
  1. $ cargo test
  2. running 1 test
  3. test tests::ignored_test ... ignored
  4. test result: ok. 0 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out
  5. Doc-tests tmp-ignore
  6. running 0 tests
  7. test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
  8. $ cargo test -- --ignored
  9. running 1 test
  10. test tests::ignored_test ... ok
  11. test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
  12. Doc-tests tmp-ignore
  13. running 0 tests
  14. test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out