Sling Academy
Home/Rust/Testing Rust Functions with #[test] and assert!

Testing Rust Functions with #[test] and assert!

Last updated: January 03, 2025

Rust, a systems programming language known for its safety and concurrency features, offers an excellent test framework built directly into its projects. This framework primarily utilizes the #[test] attribute to identify test functions. In this article, we will explore how to write and run tests in Rust using the #[test] attribute and the assert! macro.

Introduction to Rust Testing

Rust’s approach to testing is simple and effective. By adding the #[test] attribute to a function, Rust treats that function as a test. Each test function should expect nothing passed to it and doesn't return anything either. Tests are typically placed in a separate module within the same source file as the code.

Setting Up Your Test Environment

To begin using tests in a Rust project, ensure you have a basic Rust project set up. You can create one using Cargo, Rust’s package manager:

cargo new rust_testing_example

This command sets up a project directory with the needed files. Navigate into the project directory:

cd rust_testing_example

Writing Your First Test

Let’s write a simple function and a test for it. Assume we have a function that adds two numbers together:

// src/lib.rs
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

Now, we'll write a test for this function. The test will confirm that our add function behaves as expected.

// src/lib.rs
#[cfg(test)]
mod tests {
    use super::*;

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

Here’s what's happening in the test:

  • #[cfg(test)]: Only compiles the module when you run tests.
  • use super::*;: Imports all available items from the parent module.
  • #[test]: Prepares the function to be a test case.
  • assert_eq!(...): Asserts that the result of add(2, 3) is equal to 5.

Running Tests

With the function and its test in place, use Cargo to run your tests by executing:

cargo test

Upon running, Cargo will compile the library code and test code, and then execute the test. You'll receive an output indicating the results, which should look similar to:


running 1 test
test tests::test_add ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

If any assertion inside a test function fails, it prints an error report to ease debugging.

Understanding assert! and assert_eq!

The assert! macro forms the basis of Rust testing. Variants like assert_eq! and assert_ne! provide specific equality checks. Here is more about them:

  • assert!(condition): Fails if the condition is false. It helps ensure critical conditions hold.
  • assert_eq!(left, right): Fails if left is not equal to right. Useful for checking precise matches.
  • assert_ne!(left, right): Fails if left is equal to right. Useful for ensuring elements are distinct.

Handling Test Panics

By default, if an assertion fails, Rust will 'panic', immediately halting the running test. You can see useful stack traces provided by Rust to diagnose these failures quickly.

Test Organization Strategies

For larger projects, organizing your tests might become necessary. Here are a few tips:

  • **Integration Tests**: Place these inside the tests directory outside of the src directory. They test public interfaces.
  • **Documentation Tests**: Rust can create tests from documentation comments. Ideal for ensuring examples in docs remain valid.

Conclusion

Testing in Rust with #[test] and assert! is straightforward, yet powerful. As you expand your Rust projects, leveraging its potent test tools will contribute to safer, more reliable software.

Next Article: Asynchronous Functions and async/await in Rust

Previous Article: Decomposing Large Functions into Smaller Units in Rust

Series: Working with Functions in Rust

Rust

You May Also Like

  • E0557 in Rust: Feature Has Been Removed or Is Unavailable in the Stable Channel
  • Network Protocol Handling Concurrency in Rust with async/await
  • Using the anyhow and thiserror Crates for Better Rust Error Tests
  • Rust - Investigating partial moves when pattern matching on vector or HashMap elements
  • Rust - Handling nested or hierarchical HashMaps for complex data relationships
  • Rust - Combining multiple HashMaps by merging keys and values
  • Composing Functionality in Rust Through Multiple Trait Bounds
  • E0437 in Rust: Unexpected `#` in macro invocation or attribute
  • Integrating I/O and Networking in Rust’s Async Concurrency
  • E0178 in Rust: Conflicting implementations of the same trait for a type
  • Utilizing a Reactor Pattern in Rust for Event-Driven Architectures
  • Parallelizing CPU-Intensive Work with Rust’s rayon Crate
  • Managing WebSocket Connections in Rust for Real-Time Apps
  • Downloading Files in Rust via HTTP for CLI Tools
  • Mocking Network Calls in Rust Tests with the surf or reqwest Crates
  • Rust - Designing advanced concurrency abstractions using generic channels or locks
  • Managing code expansion in debug builds with heavy usage of generics in Rust
  • Implementing parse-from-string logic for generic numeric types in Rust
  • Rust.- Refining trait bounds at implementation time for more specialized behavior