---
title: "14.3 Organizing unit tests"
description: "The conventional #[cfg(test)] mod tests pattern, why tests live next to the code they test, and how unit tests can reach a module's private functions."
url: "https://learnrust.net/chapter-14/organizing-unit-tests/"
last_updated: "2026-06-13"
---

# 14.3 Organizing unit tests

Loose `#[test]` functions work, but real projects organize them. Rust has a strong convention for **unit tests**, tests that check one small piece of code in isolation, and following it puts your tests next to the code they exercise while keeping them out of the shipped binary. This lesson covers that convention and a question it makes easy: should unit tests reach into a module's private internals?

## The #[cfg(test)] mod tests convention

The idiomatic place for a module's unit tests is a submodule named `tests`, inside the same file, marked `#[cfg(test)]`:

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

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn adds_positives() {
        assert_eq!(add(2, 3), 5);
    }

    #[test]
    fn adds_with_zero() {
        assert_eq!(add(5, 0), 5);
    }
}
```

Three pieces are doing work here. `mod tests` is an ordinary module (lesson [13.2](https://learnrust.net/chapter-13/defining-modules/)) that groups the tests. `#[cfg(test)]` is the important attribute: it tells the compiler to include this module *only* when building for tests (`cargo test`), and to skip it entirely for a normal `cargo build`. So your tests add nothing to the shipped binary, no size, no compile cost in release builds, while living right beside the code. And `use super::*;` brings everything from the parent module (the `add` function) into the test module's scope, so the tests can call it without long paths; `super` is the parent-module path from lesson [13.2](https://learnrust.net/chapter-13/defining-modules/).

```
running 2 tests
test tests::adds_positives ... ok
test tests::adds_with_zero ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
```

Note the names now read `tests::adds_positives`: the test's path includes the module, exactly like any other item.

> **Key insight**
>
> `#[cfg(test)]` is conditional compilation: the `tests` module exists during `cargo test` and vanishes during `cargo build`. That's why unit tests can sit in the same file as the code without bloating your release binary or slowing normal builds. Tests live next to what they test (easy to find, easy to keep in sync) but cost nothing when you're not testing.

## Why keep tests in the same file

Putting unit tests in the same file as the code, rather than off in a separate directory, is deliberate. The test is documentation of how that code is meant to behave, and keeping it adjacent means a reader sees the examples right there, and a person changing the function sees the tests they need to keep passing. It also grants a capability the next section explains: same-file tests can see the module's *private* items.

## Testing private functions

Here's where Rust's choice differs from many languages. Because the `tests` submodule is a *child* of the module it's testing, it can access that module's private functions (lesson [13.3](https://learnrust.net/chapter-13/visibility-pub/): a child module can see its ancestors' private items). So unit tests can test internal helpers directly:

```rust
fn normalize(text: &str) -> String {
    text.trim().to_lowercase()
}

pub fn matches(query: &str, text: &str) -> bool {
    normalize(text).contains(&normalize(query))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn normalize_trims_and_lowercases() {
        assert_eq!(normalize("  HELLO  "), "hello");   // tests a private fn
    }

    #[test]
    fn matches_is_case_insensitive() {
        assert!(matches("RUST", "Learning rust"));
    }
}
```

`normalize` is private, yet `normalize_trims_and_lowercases` tests it directly, because the test module is nested inside the same module. This is occasionally exactly what you want: a tricky internal helper deserves its own focused tests.

Whether you *should* test private functions is a genuine debate. One camp says test only the public API, on the grounds that private functions are implementation details you should be free to change, and tests tied to them break on every refactor. The other camp says a complex private helper is worth pinning down directly, because a bug there is easier to localize with a targeted test than to chase through the public function that calls it. The reasonable middle: test the public API thoroughly (that's what callers depend on), and add private-function tests sparingly, only for internal logic intricate enough to earn its own safety net.

> **Best practice**
>
> Default to testing through the public API, which is what your users actually rely on and what survives refactoring. Reach into private functions only when a helper is complex enough that a direct test genuinely helps localize bugs. If you find yourself needing to test many private functions, it can be a hint that some of them want to become their own public, separately-tested module (lesson [13.4](https://learnrust.net/chapter-13/splitting-code-into-files/)).

## Quiz time

**Question #1**

What does `#[cfg(test)]` on the `mod tests` block do, and why is it important?

<details class="solution">
<summary>Show solution</summary>

It compiles the module only when building for tests (`cargo test`) and excludes it from a normal `cargo build`. This lets unit tests live in the same file as the code they test without adding anything to the shipped binary or slowing release builds. It's conditional compilation.

</details>

**Question #2**

What does `use super::*;` accomplish at the top of a `tests` module?

<details class="solution">
<summary>Show solution</summary>

It brings everything from the parent module (the code being tested) into the test module's scope, so the tests can call those functions and use those types by their short names instead of full paths. `super` refers to the parent module (lesson [13.2](https://learnrust.net/chapter-13/defining-modules/)).

</details>

**Question #3**

Why can a `#[cfg(test)] mod tests` block test a *private* function, and what's the usual advice about doing so?

<details class="solution">
<summary>Show solution</summary>

Because the `tests` module is a child of the module containing the private function, and child modules can access their ancestors' private items (lesson [13.3](https://learnrust.net/chapter-13/visibility-pub/)). The usual advice: prefer testing the public API (what callers depend on, and what survives refactoring), and test private functions only sparingly, when an internal helper is complex enough to deserve its own focused test.

</details>

Unit tests live beside the code and can see its internals. The opposite approach, testing only what an outside user can see, is the next lesson: integration tests in the `tests/` directory, where the `lib.rs` split from chapter 13 finally earns its keep.

## Sitemap

See the full [sitemap](https://learnrust.net/sitemap.md) for all pages.
