🚀
12. Testing

Testing

$ cargo new adder --lib
  • create a new library project named adder.
      • lib.rs
    • Cargo.lock
    • Cargo.toml
    • lib.rs is the crate root of the library crate.
    pub fn add(left: usize, right: usize) -> usize {
        left + right
    }
     
    #[cfg(test)]
    mod tests {
        use super::*;
     
        #[test]
        fn it_works() {
            let result = add(2, 2);
            assert_eq!(result, 4);
        }
    }
    1. assert! macro is used to check if the result of the function is true.
    2. assert_eq! macro is used to compare the result of the function with the expected value.
    3. assert_ne! macro is used to check if the two values are not equal.
    • #[cfg(test)] attribute is used to compile the test code only when running tests.
    • #[test] attribute is used to define a test function.
    $ cargo test
     
    running 1 test
    test tests::it_works ... ok
     
    test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
    • A test fails when something inside the test function panics.
    • Each test function runs in a new thread, if the main thread sees that the new thread has panicked, it reports the test as failed.
    pub fn add(left: usize, right: usize) -> usize {
        left + right
    }
     
    #[cfg(test)]
    mod tests {
        use super::*;
     
        #[test]
        fn it_works() {
            let result = add(2, 2);
            assert_eq!(result, 4);
        }
     
        #[test]
        fn failing_test() {
            panic!("Test failed");
        }
    }
    $ cargo test
     
    running 2 tests
    test tests::it_works ... ok
    test tests::failing_test ... FAILED
     
    failures:
     
    ---- tests::failing_test stdout ----
    thread 'tests::failing_test' panicked at src\lib.rs:17:9:
    Test failed
    struct Rectangle {
        width: i32,
        height: i32,
    }
     
    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 it_works() {
            let rect = Rectangle {
                width: 5,
                height: 4,
            };
     
            let small_rect = Rectangle {
                width: 4,
                height: 3,
            };
     
            assert!(rect.can_hold(&small_rect));
        }
    }
    $ cargo test
     
    running 1 test
    test tests::it_works ... ok
     
    successes:
     
    successes:
        tests::it_works
     
    test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
    • use super::* brings the Rectangle struct into tests module scope.

    Custom Failure Messages

    pub fn add(left: usize, right: usize) -> usize {
        left + right
    }
     
    #[cfg(test)]
    mod tests {
        use super::*;
     
        #[test]
        fn add_working() {
            let result = add(2, 2);
            assert_eq!(result, 5, "2 + 2 should be 5, not {}", result);
        }
    }
    $ cargo test
     
    running 1 test
    test tests::add_working ... FAILED
     
    successes:
     
    failures:
     
    ---- tests::add_working stdout ----
    thread 'tests::add_working' panicked at src\lib.rs:63:9:
    assertion `left == right` failed: 2 + 2 should be 5, not 4

    Asserting that a Function Panics

    #[cfg(test)]
    mod tests {
        use super::*;
     
        #[test]
        #[should_panic]
        fn add_working() {
            let result = add(2, 2);
            assert_eq!(result, 5, "2 + 2 should be 5, not {}", result);
        }
    }
    $ cargo test
     
    running 2 tests
    test tests::it_works ... ok
    test tests::add_working - should panic ... ok
     
    test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
    • #[should_panic] attribute is used to check if the test function panics.
    pub struct Guess {
        value: i32,
    }
     
    impl Guess {
        pub fn new(value: i32) -> Guess {
            if value < 1 {
                panic!("Guess Value must be greater than 0, got {}", value);
            } else if value > 100 {
                panic!("Guess Value must be less than 101, got {}", value);
            }
     
            Guess { value }
        }
    }
     
    #[cfg(test)]
    mod tests {
        use super::*;
     
        #[test]
        #[should_panic(expected = "Guess Value must be less than 101")]
        fn valid_guess() {
            Guess::new(101);
        }
     
        #[test]
        #[should_panic(expected = "Guess Value must be less than 101")]
        fn valid_guess2() {
            Guess::new(0);
        }
    }
    $ cargo test
     
    running 2 tests
    test tests::valid_guess - should panic ... ok
    test tests::valid_guess2 - should panic ... FAILED
     
    failures:
     
    ---- tests::valid_guess2 stdout ----
    thread 'tests::valid_guess2' panicked at src\lib.rs:73:13:
    Guess Value must be greater than 0, got 0
    note: panic did not contain expected string
          panic message: `"Guess Value must be greater than 0, got 0"`,
     expected substring: `"Guess Value must be less than 101"`
    • #[should_panic(expected = "message")] attribute is used to check if the test function panics with the expected message.

    Returning Result Types

    #[cfg(test)]
    mod tests {
        #[test]
        fn it_works() -> Result<(), String> {
            if 2 + 3 == 4 {
                Ok(())
            } else {
                Err(String::from("2 plus 2 does not equal four"))
            }
        }
    }
     
    $ cargo test
     
    running 1 test
    test tests::it_works ... FAILED
     
    failures:
     
    ---- tests::it_works stdout ----
    Error: "2 plus 2 does not equal four"

    Showing Output

    fn prints_and_return_10(a: i32) -> i32 {
        println!("I got the value {}", a);
     
        10
    }
     
    #[cfg(test)]
    mod tests {
        use super::*;
     
        #[test]
        fn this_test_will_pass() {
            let value = prints_and_return_10(4);
     
            assert_eq!(10, value);
        }
     
        #[test]
        fn this_test_will_fail() {
            let value = prints_and_return_10(8);
            assert_eq!(5, value)
        }
    }
    $ cargo test -- --show-output
    
    running 2 tests
    test tests::this_test_will_pass ... ok
    test tests::this_test_will_fail ... FAILED
    
    failures:
    
    ---- tests::this_test_will_fail stdout ----
    I got the value 8
    thread 'tests::this_test_will_fail' panicked at src\lib.rs:131:9:
    assertion `left == right` failed
      left: 5
     right: 10
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

    Running specific tests

    $ cargo test this_test_will_pass
     
    running 1 test
    test tests::this_test_will_pass ... ok
     
    test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.00s
    • cargo test test_name is used to run a specific test, which starts with the name test_name.

    Ignoring Tests

    #[cfg(test)]
    mod tests {
        use super::*;
     
        #[test]
        fn it_works() {
            assert_eq!(2 + 2, 4);
        }
     
        #[test]
        #[ignore]
        fn expensive_test() {
            // code that takes an hour to run
        }
    }
    $ cargo test
     
    running 1 test
    test tests::it_works ... ok
     
    test result: ok. 1 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.00s
    • #[ignore] attribute is used to ignore the test function.

    Running Ignored Tests

    $ cargo test -- --ignored
     
    running 1 test
    test tests::expensive_test ... ignored
     
    test result: ok. 0 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.00s

    Test Organization

    • Unit tests are small and more focused, test one module in isolation and test private interfaces.
    • Integration tests are larger and test the entire program, test the public interfaces that the library provides to other libraries.
      • integration_test.rs
  • use adder;
     
    fn it_adds_two(){
        assert_eq!(adder::add(2, 2), 4);
    }
    $ cargo test
     
    running 1 test
    test tests::internal ... ok
     
    test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
     
         Running tests\integration_test.rs (target\debug\deps\integration_test-545fb9ae521fbad3.exe)
     
    running 1 test
    test it_adds_two ... ok
     
    test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
     
       Doc-tests adder
     
    running 0 tests
     
    test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
    $ cargo test --test integration_test
     
    running 1 test
    test it_adds_two ... ok
     
    test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
    • cargo test --test test_name is used to run a specific integration test.

    • tests directory treats each file as a separate crate, (this leads unexpected behavior when using use super::*).

    • multiple integration test files and you want to share common code, you can put the common code into a tests/common.rs File

      • common.rs
      • integration_test.rs
  • pub fn setup() {
        // setup code
    }
     
    $ cargo test
     
    running 1 test
    test tests::internal ... ok
     
    test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
     
         Running tests\common.rs (target\debug\deps\common-6493fd2ebc909690.exe)
     
    running 0 tests
     
    test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
     
         Running tests\integration_test.rs (target\debug\deps\integration_test-545fb9ae521fbad3.exe)
     
    running 1 test
    test it_adds_two ... ok
     
    test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
     
       Doc-tests adder
     
    running 0 tests
     
    test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
    • Here common.rs is treated like a integration test file(not intended).

    To avoid this issue

        • mod.rs
      • integration_test.rs
  • pub fn setup() {
        // setup code
    }
     
    $ cargo test
     
    running 1 test
    test tests::internal ... ok
     
    test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
     
         Running tests\integration_test.rs (target\debug\deps\integration_test-545fb9ae521fbad3.exe)
     
    running 1 test
    test it_adds_two ... ok
     
    test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
     
       Doc-tests adder
     
    running 0 tests
     
    test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
    • common directory is treated as a module not as test crate, so it doesn't run as a test.
    • common/mod.rs allows to use the common code in the integration test file.
      • integration_test.rs
  • use adder;
     
    mod common;
     
    #[test]
    fn it_adds_two() {
        common::setup();
     
        assert_eq!(4, adder::add_two(2))
     
    }
     
    • this way the common code can be shared between multiple integration test files.
    ⚠️
    • lib.rs creates a library crate.
    • main.rs creates an binary crate.

    Cannot test binary crates with the integration tests.

    © 2024 Driptanil Datta.All rights reserved

    Made with Love ❤️

    Last updated on Mon Oct 20 2025