24.10 Summary

Testing and benchmarking are integral to developing reliable and efficient Rust software, complementing the language’s compile-time safety guarantees.

  • Purpose of Testing: Verifies logical correctness, behavior against requirements, and prevents regressions, going beyond the memory safety enforced by the compiler. Rust’s integrated tooling simplifies test creation and execution compared to typical C/C++ workflows.
  • Basic Tests: Functions marked #[test] are run by cargo test. Use assert!, assert_eq!, assert_ne! macros to check conditions. Tests fail on panic.
  • Test Organization:
    • Unit Tests: Reside in #[cfg(test)] mod tests within src/ files. Can test private items.
    • Integration Tests: Located in the tests/ directory. Each file is a separate crate testing only the public API.
  • Execution Control: Filter tests by name (cargo test <filter>), run specific test files (--test <name>), control parallelism (--test-threads=1), manage output (--nocapture), and skip tests (#[ignore], -- --ignored).
  • Testing Failures: Use #[should_panic] (optionally with expected = "...") to verify intended panics. Test functions can return Result<(), E> to use ? and test error paths cleanly.
  • Documentation Tests: Code examples (```) in doc comments are tested by cargo test, ensuring documentation stays valid. Use # to hide setup lines.
  • Test-Only Dependencies: Specified under [dev-dependencies] in Cargo.toml for helper crates not needed in the final library or binary.
  • Benchmarking: Measures code performance. Use stable crates like criterion or divan (cargo bench) for reliable results and analysis.
  • Profiling: Identifies performance bottlenecks in the application using external tools. Essential for targeted optimization.

By adopting disciplined testing and benchmarking practices, developers can leverage Rust’s strengths to build software that is not only safe but also correct and performant.