Writing Tests in Rust: Unit Tests and Integration Tests
Course Title: Mastering Rust: From Basics to Systems Programming Section Title: Testing and Documentation in Rust Topic: Writing tests in Rust: unit tests and integration tests
Introduction
Testing is an essential part of software development, ensuring that our code works as expected and catching bugs before they reach production. In Rust, testing is a first-class citizen, and the language provides a built-in testing framework that makes writing tests easy and convenient.
Writing Unit Tests in Rust
Unit tests are used to test individual units of code, typically functions or methods. In Rust, unit tests are written using the #[test]
attribute, which tells the compiler to compile the test as a separate entity from the rest of the code.
Here's an example of a simple unit test:
// src/lib.rs
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
// src/lib.rs (continued)
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(add(2, 2), 4);
}
}
In this example, we define a function add
that takes two i32
arguments and returns their sum. We then define a test module tests
with a single test function test_add
. The test function uses the assert_eq!
macro to check that the output of the add
function is correct.
Writing Integration Tests in Rust
Integration tests are used to test how different parts of the code interact with each other. In Rust, integration tests are written in the same way as unit tests, but they are placed in a separate file.
Here's an example of an integration test:
// src/lib.rs
pub struct Calculator {
pub value: i32,
}
impl Calculator {
pub fn new() -> Self {
Calculator { value: 0 }
}
pub fn add(&mut self, a: i32) {
self.value += a;
}
pub fn get_value(&self) -> i32 {
self.value
}
}
// tests/integration_test.rs
#[cfg(test)]
mod integration_test {
use crate::Calculator;
#[test]
fn test_calculator() {
let mut calculator = Calculator::new();
calculator.add(2);
assert_eq!(calculator.get_value(), 2);
}
}
In this example, we define a Calculator
struct with methods for adding a value and getting the current value. We then define an integration test integration_test
that tests how the add
and get_value
methods work together.
Using Test Modules and Test Functions
Test modules and test functions are used to organize tests and make them more readable. Here's an example of using test modules and functions:
// src/lib.rs
pub struct Calculator {
pub value: i32,
}
impl Calculator {
pub fn new() -> Self {
Calculator { value: 0 }
}
pub fn add(&mut self, a: i32) {
self.value += a;
}
pub fn get_value(&self) -> i32 {
self.value
}
}
// tests/integration_test.rs
#[cfg(test)]
mod integration_test {
use crate::Calculator;
mod arithmetic {
use super::*;
#[test]
fn test_add() {
let mut calculator = Calculator::new();
calculator.add(2);
assert_eq!(calculator.get_value(), 2);
}
#[test]
fn test_multiple_additions() {
let mut calculator = Calculator::new();
calculator.add(2);
calculator.add(3);
assert_eq!(calculator.get_value(), 5);
}
}
mod value {
use super::*;
#[test]
fn test_initial_value() {
let calculator = Calculator::new();
assert_eq!(calculator.get_value(), 0);
}
}
}
In this example, we define a Calculator
struct with methods for adding a value and getting the current value. We then define an integration test integration_test
that is organized into two test modules: arithmetic
and value
. Each test module has two test functions: test_add
and test_multiple_additions
in arithmetic
, and test_initial_value
in value
.
Ignoring Tests
Sometimes, we want to ignore a test. We can do this using the #[ignore]
attribute. Here's an example:
#[test]
#[ignore]
fn test_add() {
assert_eq!(add(2, 2), 4);
}
In this example, the test test_add
is marked with the #[ignore]
attribute, which tells the compiler to ignore the test.
Running Tests
To run tests, we can use the cargo test
command. Here's an example:
$ cargo test
This will run all the tests in our project.
Conclusion
Writing tests in Rust is a powerful way to ensure that our code works as expected. By using unit tests and integration tests, we can catch bugs before they reach production. By organizing our tests using test modules and functions, we can make our tests more readable and easier to maintain. Finally, by ignoring tests and running tests using cargo test
, we can make our testing workflow more efficient. To learn more about testing in Rust, you can refer to the official Rust documentation on testing [1].
What's Next?
In the next topic, we'll explore how to use Cargo's testing framework to write tests.
Leave a Comment or Ask for Help
Did you have trouble following the material? Please leave a comment below, and we'll do our best to help.
References:
Images

Comments