ProgrammingBackend Developer

How does the modular testing system work in Rust and why are tests closely integrated with the language itself?

Pass interviews with Hintsage AI assistant

Answer.

Background

Testing software code is one of the oldest and most important processes in the development industry. However, in many languages, testing libraries are provided separately, and integrating tests with the main code can be opaque or cumbersome. In Rust, from its earliest versions, the language was designed with built-in support for explicit modular testing.

Problem

In many traditional languages, tests are located separately or require specific frameworks and builders to be set up. This complicates collaborative development, increases the risk of desynchronization between main logic and tests, and makes it harder to perform integration and modular testing.

Solution

Rust integrates the testing system directly at the language level: the module #[cfg(test)], the directive #[test], and the tool cargo test allow for creating, running, and managing tests within the source code. This ensures a close relationship between the code and the tests that verify it, guarantees ease of test execution, and automates CI/CD processes.

Code example:

// tests automatically compiled and run by `cargo test` #[cfg(test)] mod tests { use super::*; #[test] fn it_adds_two() { assert_eq!(2 + 2, 4); } }

Key features:

  • Testing integration — tests are written in the same source tree as the main logic, making maintenance easier.
  • Ease of execution and management — one command (cargo test) runs all discovered tests.
  • Isolation of tested modules — using namespaces and the special attribute #[cfg(test)] allows tests not to be included in the release build.

Tricky Questions.

Can unstable or private functions be used in tests, and how can this be done?

Yes, they can. Inside the test module declared as #[cfg(test)], tests have access to both public and private API of the current module. This means you can test implementation details directly. However, from another module — only through the public interface.

Example:

fn private_internal(x: i32) -> i32 { x + 1 } #[cfg(test)] mod tests { use super::*; #[test] fn test_private() { assert_eq!(private_internal(41), 42); } }

Will tests with the same names conflict between different modules?

No, test names are only visible within their own module. Tests from different modules can have the same names because the compiler differentiates them by their full path (namespace).

Example:

mod a { #[cfg(test)] mod tests { #[test] fn basic() {} } } mod b { #[cfg(test)] mod tests { #[test] fn basic() {} } }

Can a single test or part of a set of tests be run separately?

Yes, through test name filtering: cargo test test_name. This is convenient for debugging large sets.

Example:

cargo test only_this_test

Common mistakes and anti-patterns

  • Writing long, non-atomic tests that check multiple aspects of a function at once.
  • Immediately commenting out or deleting tests that start to "break" with code changes instead of looking for and fixing the underlying cause.
  • Test dependence on global state: making non-clean or irreversible actions (e.g., creating files that are not deleted).

Real-life Example

Negative Case

A developer moved tests to a separate project without access to private functions and with a different directory structure. Due to changes in the main library, tests quickly fell behind and became outdated.

Pros: Tests can be isolated, avoiding accidental inclusion of test modules in the release build.

Cons: Loss of synchronization, inability to test internal details, high maintenance, and risk of test obsolescence.

Positive Case

Tests are written right in the same modules as the main logic, small atomic test cases test the private and public API. A beginner quickly understands the module's workings through tests.

Pros: Maximum support for synchronization, transparency, faster local testing, suitability for test-driven development.

Cons: Increase in lines of code in source files, potential growth in complexity with a large number of tests.