Why do some devs use mod tests for tests?

Viewed 274

Some people do

#[cfg(test)]
mod tests {
   //all tests here
}

While simply putting every test in the end of the file like

#[test]
fn test1() {

}

#[test]
fn test2() {

}

works too and I can even run single tests by cargo.

What is the difference? Why do some people do it the first way?

2 Answers

You often need to share some code between your tests, for example macros and functions used for building the test cases or to check the result values. And you sometimes have to import whole crates just for your tests.

The #[cfg(test)] conditional compilation attribute makes it possible to have code not compiled when not testing. This enables faster checks and a faster compilation in the general case. If you include a crate just for the tests, you can import it in those mods and specify the dev dependency in a [dev-dependencies] section of your Cargo.toml.

Here's an example of such a test mod where some functions are used in several tests: wrap.rs.

I personally create a mod as soon as the tests of a function aren't trivial, even when there's not (yet) any shared help function: the test mod then appears as an easy to notice test block in my code.

Sometimes you go even further by using the "tests" directory solution for your integration tests, which makes it easier to have whole mods shared between your tests. This is often the most practical solution when your tests are more than unit tests (some crates have more tests than tested code).

Read the rust book on test organization to know more.

You don't want your code under test to be able to access any test code. By putting the tests and helper functions in a child module (and not making them pub), you prevent the code under test to access any test code.

The following sample code will help demonstrate my point:

src/add.rs

pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

pub fn compiler_test() {
    // Uh oh, below compiles!
    #[cfg(test)]
    test_add();

    // Good, error[E0603]: function `test_add` is private
    #[cfg(test)]
    tests::test_add();
    //     ^^^^^^^^ private function
}

#[test]
fn test_add() {
    assert_eq!(add(4, 5), 9)
}

#[cfg(test)]
mod tests {
    #[test]
    fn test_add() {
        assert_eq!(super::add(1, 2), 3)
    }
}

Note that skipping mod tests does not prevent you from making use of [dev-dependencies], because #[test] is enough to make compilation conditional on #[cfg(test)].

Related