In software development, testing is a crucial step to ensure that your code behaves as expected. Rust provides a robust framework for writing unit tests directly within your source files using the #[cfg(test)] attribute and the #[test] macro. This tutorial will guide you through creating unit tests for individual functions in Rust.
Unit tests are focused on testing small, isolated pieces of code, typically individual functions or methods. They help verify that each component works correctly under various conditions. In Rust, you can write these tests within the same file as the code they're testing by using the #[cfg(test)] attribute to include them only when running tests.
Let's start with a simple example. Suppose we have a function that adds two numbers:
1fn add(a: i32, b: i32) -> i32 {2a + b3}
To test this function, we can write a unit test in the same file:
1#[cfg(test)]2mod tests {3use super::*;45#[test]6fn test_add() {7assert_eq!(add(2, 3), 5);8assert_eq!(add(-1, 1), 0);9assert_eq!(add(-5, -5), -10);10}11}
In this example:
tests with the #[cfg(test)] attribute to ensure it's only compiled when running tests.use super::*; to bring all items from the parent module into scope.test_add function is marked with #[test], indicating that it's a test case.assert_eq! to check if the output of add matches the expected result.To run the tests, you can use the Cargo command-line tool:
$ cargo test
This will compile your code and execute all tests. You should see an output similar to this:
running 1 test test tests::test_add ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
You can also write tests to ensure that your code panics under certain conditions:
1fn divide(a: i32, b: i32) -> i32 {2if b == 0 {3panic!("Division by zero");4}5a / b6}78#[cfg(test)]9mod tests {10use super::*;1112#[test]13fn test_divide() {14assert_eq!(divide(10, 2), 5);15assert_eq!(divide(-10, 2), -5);1617// Test for panic18let result = std::panic::catch_unwind(|| divide(10, 0));19assert!(result.is_err());20}21}
In this example:
divide function panics if the divisor is zero.std::panic::catch_unwind to test for a panic condition.Sometimes, you might need to set up some state before running tests and clean it up afterward. Rust provides the before_each and after_each hooks in the form of setup functions:
1struct Counter {2count: i32,3}45impl Counter {6fn new() -> Counter {7Counter { count: 0 }8}910fn increment(&mut self) {11self.count += 1;12}1314fn get_count(&self) -> i32 {15self.count16}17}1819#[cfg(test)]20mod tests {21use super::*;2223#[test]24fn test_counter() {25let mut counter = Counter::new();26assert_eq!(counter.get_count(), 0);2728counter.increment();29assert_eq!(counter.get_count(), 1);30}31}
In this example:
Counter struct with methods to increment and get the count.Counter, increments it, and checks the count.Now that you've learned how to write unit tests for individual functions in Rust, the next step is to explore integration tests. Integration tests focus on testing how different parts of your application work together. They are typically written in a separate directory and can test more complex scenarios involving multiple modules or even external dependencies.
Stay tuned for the next tutorial where we'll dive into writing integration tests!