Skip to content

docs(dev/guide): document the external-file unit-test module convention #395

@KSXGitHub

Description

@KSXGitHub

This is PR from an unrelated code-base:

Diff
diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index b51d6d8..0252027 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -14,6 +14,7 @@ Read and follow the CONTRIBUTING.md file in this repository for all code style c
 - Prefer `where` clauses when a type has multiple trait bounds.
 - Minimize `unwrap()` in non-test code. Use proper error handling instead.
 - Prefer `#[cfg_attr(..., ignore = "reason")]` over `#[cfg(...)]` when skipping tests. Use `#[cfg]` on tests only when the code cannot compile under the condition, such as when it references types or functions that do not exist on other platforms.
+- A unit-test module may sit inline as `mod tests { ... }` when it is short, but once it grows long enough to noticeably extend the length of the parent, move it into a dedicated external file and declare the external `tests` module with `#[cfg(test)] mod tests;` at the end of the parent. For `src/foo.rs` the tests file is `src/foo/tests.rs`, and for `src/foo/bar.rs` it is `src/foo/bar/tests.rs`. Use this layout even when the parent has no other submodules.
 - In test modules, prefer explicit brace lists such as `use super::{Foo, Bar};` over `use super::*;` so each symbol under test is declared. Import items that live outside the direct parent module by their canonical path (for example, `use crate::bar::SomeType;`) rather than through a name the parent happens to bring into its own scope with `use` or `pub use`. These rules apply equally to inline `#[cfg(test)] mod tests { ... }` blocks and external `src/<module>/tests.rs` files.
 - Install the toolchain before running tests: `rustup toolchain install "$(< rust-toolchain)" && rustup component add --toolchain "$(< rust-toolchain)" rustfmt clippy`.
 - Validate changes with `cargo fmt -- --check && cargo clippy --all-targets && cargo test`.
diff --git a/AGENTS.md b/AGENTS.md
index 7442b5b..71b05bc 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -14,6 +14,7 @@ Read and follow the CONTRIBUTING.md file in this repository for all code style c
 - Prefer `where` clauses when a type has multiple trait bounds.
 - Minimize `unwrap()` in non-test code. Use proper error handling instead.
 - Prefer `#[cfg_attr(..., ignore = "reason")]` over `#[cfg(...)]` when skipping tests. Use `#[cfg]` on tests only when the code cannot compile under the condition, such as when it references types or functions that do not exist on other platforms.
+- A unit-test module may sit inline as `mod tests { ... }` when it is short, but once it grows long enough to noticeably extend the length of the parent, move it into a dedicated external file and declare the external `tests` module with `#[cfg(test)] mod tests;` at the end of the parent. For `src/foo.rs` the tests file is `src/foo/tests.rs`, and for `src/foo/bar.rs` it is `src/foo/bar/tests.rs`. Use this layout even when the parent has no other submodules.
 - In test modules, prefer explicit brace lists such as `use super::{Foo, Bar};` over `use super::*;` so each symbol under test is declared. Import items that live outside the direct parent module by their canonical path (for example, `use crate::bar::SomeType;`) rather than through a name the parent happens to bring into its own scope with `use` or `pub use`. These rules apply equally to inline `#[cfg(test)] mod tests { ... }` blocks and external `src/<module>/tests.rs` files.
 - Install the toolchain before running tests: `rustup toolchain install "$(< rust-toolchain)" && rustup component add --toolchain "$(< rust-toolchain)" rustfmt clippy`.
 - Validate changes with `cargo fmt -- --check && cargo clippy --all-targets && cargo test`.
diff --git a/CLAUDE.md b/CLAUDE.md
index 7442b5b..71b05bc 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -14,6 +14,7 @@ Read and follow the CONTRIBUTING.md file in this repository for all code style c
 - Prefer `where` clauses when a type has multiple trait bounds.
 - Minimize `unwrap()` in non-test code. Use proper error handling instead.
 - Prefer `#[cfg_attr(..., ignore = "reason")]` over `#[cfg(...)]` when skipping tests. Use `#[cfg]` on tests only when the code cannot compile under the condition, such as when it references types or functions that do not exist on other platforms.
+- A unit-test module may sit inline as `mod tests { ... }` when it is short, but once it grows long enough to noticeably extend the length of the parent, move it into a dedicated external file and declare the external `tests` module with `#[cfg(test)] mod tests;` at the end of the parent. For `src/foo.rs` the tests file is `src/foo/tests.rs`, and for `src/foo/bar.rs` it is `src/foo/bar/tests.rs`. Use this layout even when the parent has no other submodules.
 - In test modules, prefer explicit brace lists such as `use super::{Foo, Bar};` over `use super::*;` so each symbol under test is declared. Import items that live outside the direct parent module by their canonical path (for example, `use crate::bar::SomeType;`) rather than through a name the parent happens to bring into its own scope with `use` or `pub use`. These rules apply equally to inline `#[cfg(test)] mod tests { ... }` blocks and external `src/<module>/tests.rs` files.
 - Install the toolchain before running tests: `rustup toolchain install "$(< rust-toolchain)" && rustup component add --toolchain "$(< rust-toolchain)" rustfmt clippy`.
 - Validate changes with `cargo fmt -- --check && cargo clippy --all-targets && cargo test`.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 91f718d..d7fd346 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -235,6 +235,30 @@ let output = cmd.output().expect("spawn my-tool");
 
 Available `.with_*` methods mirror every standard builder method: `with_arg`, `with_args`, `with_env`, `with_envs`, `with_env_remove`, `with_env_clear`, `with_current_dir`, `with_stdin`, `with_stdout`, `with_stderr`.
 
+## Unit Tests
+
+A unit-test module may either sit inline as `mod tests { ... }` in its parent or live in a dedicated external `tests` submodule. The inline form is appropriate for short test modules; once the block grows long enough to noticeably extend the length of the parent and get in the way of reading the rest of the module, move the tests into an external file.
+
+### When the inline form is acceptable
+
+There is nothing wrong with `mod tests { ... }` by itself. Reserve the inline form for modules whose entire test suite fits in a small number of lines, so that the block does not noticeably extend the length of the parent. The problem that this convention addresses is a long inline block that extends the length of the main module and makes it harder to traverse. Use the number of lines as the deciding factor: once the inline tests grow long enough to get in the way of reading the rest of the module, move them into an external file.
+
+### Where the external file sits
+
+When the tests live externally, the parent declares them at the end of the file with the standard declaration:
+
+```rust
+#[cfg(test)]
+mod tests;
+```
+
+The external file itself sits in a directory named after the parent, using the same path regardless of whether the parent has any other submodules. Concretely:
+
+- For `src/foo.rs`, the tests file is `src/foo/tests.rs`.
+- For `src/foo/bar.rs`, the tests file is `src/foo/bar/tests.rs`.
+
+Do not flatten the tests into a sibling file such as `src/foo_tests.rs`, and do not skip the intermediate directory when the parent currently has no other submodules. The existing sibling pair `src/video_descriptor.rs` and `src/video_descriptor/tests.rs` illustrates this layout.
+
 ## Setup
 
 Install the required Rust toolchain and components before running any checks:
diff --git a/template/ai-instructions/shared.md b/template/ai-instructions/shared.md
index 7442b5b..71b05bc 100644
--- a/template/ai-instructions/shared.md
+++ b/template/ai-instructions/shared.md
@@ -14,6 +14,7 @@ Read and follow the CONTRIBUTING.md file in this repository for all code style c
 - Prefer `where` clauses when a type has multiple trait bounds.
 - Minimize `unwrap()` in non-test code. Use proper error handling instead.
 - Prefer `#[cfg_attr(..., ignore = "reason")]` over `#[cfg(...)]` when skipping tests. Use `#[cfg]` on tests only when the code cannot compile under the condition, such as when it references types or functions that do not exist on other platforms.
+- A unit-test module may sit inline as `mod tests { ... }` when it is short, but once it grows long enough to noticeably extend the length of the parent, move it into a dedicated external file and declare the external `tests` module with `#[cfg(test)] mod tests;` at the end of the parent. For `src/foo.rs` the tests file is `src/foo/tests.rs`, and for `src/foo/bar.rs` it is `src/foo/bar/tests.rs`. Use this layout even when the parent has no other submodules.
 - In test modules, prefer explicit brace lists such as `use super::{Foo, Bar};` over `use super::*;` so each symbol under test is declared. Import items that live outside the direct parent module by their canonical path (for example, `use crate::bar::SomeType;`) rather than through a name the parent happens to bring into its own scope with `use` or `pub use`. These rules apply equally to inline `#[cfg(test)] mod tests { ... }` blocks and external `src/<module>/tests.rs` files.
 - Install the toolchain before running tests: `rustup toolchain install "$(< rust-toolchain)" && rustup component add --toolchain "$(< rust-toolchain)" rustfmt clippy`.
 - Validate changes with `cargo fmt -- --check && cargo clippy --all-targets && cargo test`.

TASKS:

  1. Analyze the diff.

  2. Adapt the change to this repository.

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentation

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions