Table-driven tests are a powerful and efficient way to test multiple scenarios with varied inputs, especially in languages like Rust, where defining tests and executing them with different parameters can significantly enhance the readability and maintainability of your test suite. Table-driven tests involve defining a set of inputs and expected outputs, then iterating through them to execute your test function.
In this article, we will explore how to write table-driven tests in Rust using parameterized inputs. This approach is beneficial when functions need to be verified across a range of input conditions, keeping our test code concise and clear. Let’s dive into a practical example using Rust’s testing framework.
Defining a Basic Function
For the purpose of this tutorial, let's define a simple function `add`, which adds two integers.
fn add(a: i32, b: i32) -> i32 {
a + b
}
Now, we want to ensure this function works correctly by testing various integer combinations. Instead of writing a separate test for each case, we will employ table-driven tests to streamline our testing process.
Setting up Your Test Function
Rust provides a built-in module for writing tests, by default a file named tests.rs
within the src
directory can be used for these purposes.
#[cfg(test)]
mod tests {
use super::*;
struct TestCase {
input_a: i32,
input_b: i32,
expected: i32,
}
#[test]
fn test_add() {
let test_cases = vec![TestCase { input_a: 1, input_b: 2, expected: 3 },
TestCase { input_a: -1, input_b: -1, expected: -2 },
TestCase { input_a: 0, input_b: 0, expected: 0 },
TestCase { input_a: 100, input_b: 200, expected: 300 }];
for case in test_cases {
let result = add(case.input_a, case.input_b);
assert_eq!(result, case.expected);
}
}
}
In this example, a TestCase
struct is defined to hold the parameters for each test: input_a
, input_b
, and expected
, which represents the expected result. The test_add
function creates a vector of TestCase
instances, each capturing a unique test scenario.
Understanding Assertions
Within the loop over test_cases
, we call the add
function with each pair of inputs, then use assert_eq!
to compare the function's output to the expected result. If the output doesn't match the expected value, assert_eq!
will panic, marking the test as failed.
Extending Our Test with More Cases
The structure of this test setup makes it trivial to extend with new test cases. Simply append new cases to the test_cases
vector:
let more_cases = vec![
TestCase { input_a: 5, input_b: -5, expected: 0 },
TestCase { input_a: -10, input_b: 15, expected: 5 },
// Add more cases as needed
];
This flexibility ensures your test coverage can easily grow alongside your code.
Advantages of Table-Driven Tests
- Conciseness: Allows packing a vast range of scenarios into a single test function.
- Maintainability: New cases can be added only by modifying the input data, promoting easy test expansion.
- Readability: A clear, organized format makes it easier for other developers to understand the scenarios tested.
By using table-driven testing in Rust, you enhance both the flexibility and reliability of your tests. This methodology provides the ability to effortlessly test different conditions and discover hidden edge cases.
Get started incorporating table-driven tests in your Rust projects today, and see for yourself how it can simplify your testing routines!