Sling Academy
Home/Rust/Writing Table-Driven Tests in Rust for Parameterized Inputs

Writing Table-Driven Tests in Rust for Parameterized Inputs

Last updated: January 06, 2025

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!

Next Article: Mocking and Faking Dependencies in Rust Tests

Previous Article: Using #[should_panic] in Rust for Expected Failures

Series: Testing 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