Labs ICT
Pro Login

Testing

Unit tests and integration tests.

Testing in Rust

Rust has excellent built-in testing support. You write tests as functions with the #[test] attribute, and run them with cargo test.

Tests are just regular functions that assert conditions. If an assertion fails, the test fails. If it succeeds, the test passes.

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        let result = 2 + 2;
        assert_eq!(result, 4);
    }
}

fn main() {
    println!("Run tests with: cargo test");
}
Try it Yourself ->

Assertion Macros

Rust provides three main assertion macros: assert!, assert_eq!, and assert_ne!. They check conditions and panic with helpful messages if they fail.

#[cfg(test)]
mod tests {
    #[test]
    fn test_assertions() {
        let x = 5;
        assert!(x > 0);
        assert_eq!(x, 5);
        assert_ne!(x, 10);

        let name = "Rust";
        assert!(name.contains("ust"));
        assert_eq!(name.len(), 4);
    }
}

fn main() {
    println!("Assertion macros: assert!, assert_eq!, assert_ne!");
}
Try it Yourself ->

Testing with Result

Tests can return Result<T, E> instead of panicking. This is useful when you want to use the ? operator in tests.

Try it Yourself ->

Unit Tests in the Same File

Unit tests go in a tests module at the bottom of your source file. This lets them test private functions directly.

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

fn multiply(a: i32, b: i32) -> i32 {
    a * b
}

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

    #[test]
    fn test_add() {
        assert_eq!(add(2, 3), 5);
        assert_eq!(add(-1, 1), 0);
    }

    #[test]
    fn test_multiply() {
        assert_eq!(multiply(2, 3), 6);
        assert_eq!(multiply(-1, 1), -1);
    }
}

fn main() {
    println!("Unit tests go in a tests module in the same file");
}
Try it Yourself ->

Integration Tests

Integration tests go in the tests/ directory. They test your library from the outside, just like a user would. Each file is a separate test crate.

// In tests/integration_test.rs:
// use my_crate::add;
//
// #[test]
// fn test_add_integration() {
//     assert_eq!(add(2, 3), 5);
// }

fn main() {
    println!("Integration tests go in the tests/ directory");
    println!("Each file is a separate test crate");
}
Try it Yourself ->

#[should_panic]

Use #[should_panic] to test that code panics as expected. This is useful for testing error conditions.

fn divide(a: f64, b: f64) -> f64 {
    if b == 0.0 {
        panic!("Cannot divide by zero");
    }
    a / b
}

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

    #[test]
    #[should_panic(expected = "Cannot divide by zero")]
    fn test_divide_by_zero() {
        divide(10.0, 0.0);
    }
}

fn main() {
    println!("Use #[should_panic] to test expected panics");
}
Try it Yourself ->

Test Organization Best Practices

Put unit tests in the same file as the code they test. Put integration tests in the tests/ directory. Use descriptive test names that explain what's being tested.

Run cargo test to run all tests, cargo test -- --nocapture to see println! output, and cargo test test_name to run specific tests.