Testing in Rust

Testing is an essential part of software development, and Rust provides built-in support for writing and running tests. Rust’s testing framework is integrated into the language and tooling, making it easy to write unit tests, integration tests, and documentation tests.

Core Concepts

1. Unit Tests

Unit tests are small, focused tests that check the correctness of individual functions or modules. They are typically placed in the same file as the code being tested, inside a special #[cfg(test)] module.

Example:

// src/lib.rs

pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_add() {
        assert_eq!(add(2, 3), 5);
    }
}
  • #[test] marks a function as a test.
  • assert_eq! checks that two values are equal; if not, the test fails.

2. Integration Tests

Integration tests check how different parts of your library work together. They are placed in the tests directory at the root of your project, and each file is a separate test crate.

Example:

// tests/integration_test.rs

use my_crate::add;

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

3. Running Tests

To run all tests in your project, use:

cargo test

This command compiles your code in test mode and runs all functions marked with #[test].

4. Common Test Macros

  • assert!: Checks if a condition is true.
  • assert_eq!: Checks if two values are equal.
  • assert_ne!: Checks if two values are not equal.
  • should_panic: Marks a test that should panic.

Example:

#[test]
fn test_should_panic() {
    panic!("This test will fail");
}

#[test]
#[should_panic]
fn test_expected_panic() {
    panic!("This test will pass because it panics");
}

5. Ignoring Tests

You can temporarily ignore a test using the #[ignore] attribute.

#[test]
#[ignore]
fn expensive_test() {
    // code that takes a long time to run
}

Run ignored tests with:

cargo test -- --ignored

6. Testing for Errors with Result

Tests can return Result<(), E>, allowing you to use the ? operator for error handling.

#[test]
fn test_result() -> Result<(), String> {
    if 2 + 2 == 4 {
        Ok(())
    } else {
        Err(String::from("Math is broken!"))
    }
}

Summary

  • Rust has first-class support for testing with built-in attributes and macros.
  • Unit tests are placed in the same file as the code; integration tests go in the tests directory.
  • Use cargo test to run all tests.
  • Use assertions to check conditions and expected results.
  • You can write tests that expect panics or return Result.

Testing is a key part of writing reliable Rust code!