rust-test-separate
Skill1 starsChanged 4 months agoMITView on GitHub
Separates inline #[cfg(test)] modules from Rust implementation files into dedicated test files. Use when user says 'separate tests', 'extract tests', 'move tests to separate files', 'split tests from implementation', 'reorganize rust tests', or asks about test file organization in Rust. Do NOT use for writing new tests, running tests, or non-Rust codebases.
In this repository24 other files it gives its agents.
- app-hardenSkill
- arch-auditSkill
- arch-conformanceSkill
- ballot-advisorSkill
- bug-huntSkill
- canon-c50Skill
- canon-r6-mark-iiSkill
- code-scrutinySkill
- davinci-resolveSkill
- desktop-ui-craftSkill
- fsharp-upliftSkill
- pattern-architectSkill
- pine-script-proSkill
- plan-evalSkill
- plan-executeSkill
- plan-featureSkill
- refactor-proSkill
- rust-ci-greenSkill
- rust-commitSkill
- rust-upliftSkill
- rust-warn-fixSkill
- skill-forgeSkill
- skill-refineSkill
- sprintSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
24 rules. About 0 are common, 11 are distinctive, and 13 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈11
Distinctive · estimate
This author's own choices. Worth a look.
13
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 18
3. Always keep
#[cfg(test)]on the module declaration: After extraction, the implementation file must have #[cfg(test)] mod tests. - Very specific: line 20
4. Don't separate small test modules: If the #[cfg(test)] mod tests block is under ~150 lines, leave it inline.
- Very specific: line 28
Single file: If $ARGUMENTS is a .rs file path, read that file directly.
- Distinctive: line 60
Extract: Test block >= 150 lines (or user explicitly requested this file)
- Distinctive: line 61
Skip: Test block < 150 lines, user didn't specifically request it, or crate root file without explicit confirmation
- Distinctive: line 64
Wait for user confirmation before executing.
- Very specific: line 77
Create src/foo/tests.rs (or appropriate path).
- Distinctive: line 78
If it already contains the separated tests (idempotent re-run), skip this file.
- Distinctive: line 79
If it exists with different content, report to the user and stop — don't overwrite.
- Very specific: line 81
Write the file with the contents of the mod tests block.
- Distinctive: line 86
All helper functions, fixtures, and constants from within the test module
- Very specific: line 87
All inner attributes (e.g., #![allow(unused)]) from the test module
- Distinctive: line 88
All sub-modules within the test module
- Very specific: line 103
Preserve outer attributes: If the original mod tests had outer attributes (e.g., #[allow(clippy::...)]) above #[cfg(test)], transfer them …
- Very specific: line 112
mod.rsconvention: Create foo/ directory, write the edited implementation content to foo/mod.rs, then delete the original foo.rs only … - Very specific: line 116
If the implementation file has #[cfg(test)] use ...
- Very specific: line 126
If Case A restructured a file (e.g., src/foo.rs → src/foo/mod.rs), check Cargo.toml for explicit path entries (in [lib], [[bin]], …
- Distinctive: line 130
After all extractions, run:
- Distinctive: line 135
If there are errors:
- Distinctive: line 136
Read the error output carefully
- Very specific: line 139
If a fix requires making a private item pub(crate), flag this to the user — it may indicate the test should actually remain inline
- Distinctive: line 145
Before any file moves, determine which module convention the project uses:
- Very specific: line 159
Check existing directory modules in the project.
- Very specific: line 167
#[cfg(test)]onimplblocks: Leave these in the implementation file.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Rust Test Separator
You separate inline #[cfg(test)] mod tests { ... } blocks from Rust implementation files into dedicated test files, following the conventions used by tokio, bevy, and axum.
Critical Principles
1. Preserve the module hierarchy. The extracted test module must remain a child of the implementation module so use super::* continues to work and private function access is retained. This means using the foo.rs + foo/tests.rs pattern (or #[cfg(test)] mod tests; declaration), NOT moving tests to tests/ at the top level.
2. Never move unit tests to tests/ (integration test directory). The tests/ directory creates separate crates that cannot access private or pub(crate) items. Only integration tests belong there. This skill handles unit test extraction only.
3. Always keep #[cfg(test)] on the module declaration. After extraction, the implementation file must have #[cfg(test)] mod tests; — without #[cfg(test)], the test file compiles into release builds.
4. Don't separate small test modules. If the #[cfg(test)] mod tests block is under ~150 lines, leave it inline. Extraction adds indirection without meaningful benefit. The user can override this with explicit instructions.
Process
Phase 1: Scan, Plan & Confirm
Determine the target scope from $ARGUMENTS:
- Single file: If
$ARGUMENTSis a.rsfile path, read that file directly. Skip glob/grep. - Directory or omitted: Glob
src/**/*.rs(relative to the argument path or cwd), then grep for#[cfg(test)]as a coarse filter.
Filtering candidates. The grep is a pre-filter. When reading each file, skip it if:
- It contains
#[cfg(test)] mod tests;(semicolon, no braces) — already separated. - The
#[cfg(test)]is only on individual functions orusestatements, not amodblock. - The test module has a
#[path = "..."]attribute — warn the user and skip (non-standard layout that extraction could break).
For each valid candidate with an inline #[cfg(test)] mod ... { ... } block, record:
- File path
- Estimated line count of the test module
- Whether the file already uses directory module form (
mod.rspattern orfoo.rs+foo/directory)
Note: files in src/bin/ are valid candidates — the same extraction mechanics apply.
Crate root files (lib.rs, main.rs): Extracting from these creates src/lib/tests.rs or src/main/tests.rs, which is technically valid but unconventional. Flag these to the user with a note that most projects keep crate root tests inline, and only extract if the user explicitly confirms.
Present a table with the planned action:
| File | Test Lines | Case | Action |
|------|-----------|------|--------|
| src/parser.rs | 340 | A | Extract |
| src/lib.rs | 45 | - | Skip (small, crate root) |
| src/utils/mod.rs | 220 | B | Extract |Case definitions:
- Case A — File needs directory restructuring. The file is a standalone
foo.rswith no existingfoo/directory. Extraction requires creatingfoo/and either convertingfoo.rs→foo/mod.rs(mod.rs convention) or keepingfoo.rsand addingfoo/tests.rsalongside it (Rust 2018+ convention). - Case B — Directory already exists. The file is already
foo/mod.rsor has an existingfoo/directory. Just addtests.rsinside the existing directory.
Action criteria:
- Extract: Test block >= 150 lines (or user explicitly requested this file)
- Skip: Test block < 150 lines, user didn't specifically request it, or crate root file without explicit confirmation
- User can override any action before proceeding
Wait for user confirmation before executing.
Phase 2: Execute
For each planned extraction, execute in this order:
Step 1: Create the directory (if Case A)
mkdir -p src/fooStep 2: Write the new test file
Create src/foo/tests.rs (or appropriate path). Before creating, verify tests.rs doesn't already exist at that path. If it does:
- If it already contains the separated tests (idempotent re-run), skip this file.
- If it exists with different content, report to the user and stop — don't overwrite.
Write the file with the contents of the mod tests block. If the original block already contained use super::*;, preserve it as-is. If it did not, add use super::*; at the top.
Preserve:
- All
usestatements from within themod testsblock - All
#[test]functions exactly as they were - All helper functions, fixtures, and constants from within the test module
- All inner attributes (e.g.,
#![allow(unused)]) from the test module - All sub-modules within the test module
- The original ordering of items
Do NOT add:
#[cfg(test)]at the top of the file (themod tests;declaration in the parent handles this)- Any module-level documentation that wasn't there before
Step 3: Modify the implementation file
Replace the entire #[cfg(test)] mod tests { ... } block with:
#[cfg(test)]
mod tests;Preserve outer attributes. If the original mod tests had outer attributes (e.g., #[allow(clippy::...)]) above #[cfg(test)], transfer them to the new declaration:
#[allow(clippy::needless_pass_by_value)]
#[cfg(test)]
mod tests;File restructuring (Case A only):
- Rust 2018+ convention (default): Keep
foo.rsin place, createfoo/tests.rsalongside it. No file moves needed. mod.rsconvention: Createfoo/directory, write the edited implementation content tofoo/mod.rs, then delete the originalfoo.rsonly after verifying the new file exists. Update the parent module'smod foo;declaration if necessary.
Step 4: Move #[cfg(test)] imports
If the implementation file has #[cfg(test)] use ... statements outside the test module, move ALL of them into tests.rs. They are test-only by definition.
Path adjustments for these moved imports (NOT for imports that were already inside mod tests):
use crate::...paths need no change (absolute)use super::...paths need one extrasuper::prefix (becominguse super::super::...) because from insidetests.rs,superresolves to the implementation module, not its parent
Imports that were already inside the mod tests { } block need zero path changes — they already resolved relative to the test module's scope.
Step 5: Check Cargo.toml
If Case A restructured a file (e.g., src/foo.rs → src/foo/mod.rs), check Cargo.toml for explicit path entries (in [lib], [[bin]], [[example]], etc.) pointing to the old file path. Update any stale paths.
Step 6: Verify
After all extractions, run:
cargo check --tests 2>&1If there are errors:
- Read the error output carefully
- Common fixes: adjust
usepaths, addpub(crate)to items that tests need across module boundaries, fixsuper::paths - Apply fixes and re-check
- If a fix requires making a private item
pub(crate), flag this to the user — it may indicate the test should actually remain inline
Report the final result: which files were extracted, any visibility changes required, and the cargo check outcome. Optionally suggest running cargo test to confirm tests still pass at runtime.
Module Convention Detection
Before any file moves, determine which module convention the project uses:
mod.rs convention (traditional):
src/parser/mod.rs <- module root
src/parser/lexer.rs <- sub-moduleNon-mod.rs convention (Rust 2018+):
src/parser.rs <- module root
src/parser/lexer.rs <- sub-moduleCheck existing directory modules in the project. Use whichever convention is already in use. If the project has no directory modules yet, prefer the Rust 2018+ convention (keeping foo.rs alongside foo/ directory) unless the user specifies otherwise.
Edge Cases
- Multiple
#[cfg(test)]blocks in one file: Merge into a singletests.rs, preserving all contents. If merged blocks contain duplicate item names, report the conflict to the user rather than silently breaking compilation. - Nested
#[cfg(test)]modules (e.g.,mod testscontainingmod integration): Preserve the nesting structure intests.rs. - Test modules with names other than
tests: Preserve the original name. Use#[cfg(test)] mod original_name;in the declaration. #[cfg(test)]on individual functions (not in a module): Leave these alone — they're a different pattern.#[cfg(test)]onimplblocks: Leave these in the implementation file. These are test helper methods on the struct itself (e.g., builder methods for test fixtures) and should stay near the type definition.- Test modules using
use crate::...instead ofuse super::*: Fully qualifieduse crate::...paths need no adjustment after extraction. Onlyuse super::...paths are affected by the module depth change, and only for imports moved from outside the test module (see Step 4). - Files that are already
tests.rs: Skip. Don't re-extract. - Workspace crates: Process one crate at a time. If
$ARGUMENTSpoints to a workspace root, ask which crate(s) to process. build.rs: Excluded — it is a separate build script compilation unit, not a library/binary module.
Consult references/rust-test-module-reference.md for visibility rules, empirical project data, and rare edge case patterns.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
Reviews can't be read right now.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

