Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Testing and Doctests

Rule

Test behavior at the level where it is observable and risky: focused unit tests for local logic, integration tests for public workflows, and doctests only by explicit opt-in.

Why

Test placement should protect behavior without creating file quotas or coupling coverage to implementation structure.

Do

  • Test invariants, outputs, errors, and observable state changes.
  • Put focused logic, parsers, validation, and transformations in nearby unit tests.
  • Put public APIs, CLI behavior, I/O boundaries, and workflows in integration tests.
  • Choose placement from behavior and risk, not source-file count.
  • Name tests as behaviors such as rejects_zero_limit.
  • Return Result from tests when setup naturally uses ?.
  • Keep fixtures and helpers small and domain-named.
  • Use real values and temporary resources where practical; fake only slow, external, or nondeterministic boundaries.
  • Put regression tests where the bug was observable.

Avoid

  • Do not require a test module in every nontrivial source file.
  • Do not mirror private implementation steps or every helper.
  • Do not use rustdoc examples as default coverage.
  • Do not add sleeps or timing dependence.
  • Do not make helpers public solely for integration tests.
  • Do not add broad test-only controls to production APIs.
  • Do not settle for “does not panic” when behavior can be asserted.

Example

#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn rejects_zero_limit() {
        let error = parse_limit("0").expect_err("zero should be rejected");
        assert_eq!(error, LimitError::Zero);
    }
}
}

An end-to-end create-user workflow belongs under tests/ because callers observe it through the public application surface.

Exceptions

  • Add doctests only when the project opts into maintaining public examples.
  • Use module-private tests for dense parser, validator, state-machine, or algorithm edge cases.
  • Gate deliberate reusable test support behind a test-util feature.