agentleFS
dawidpereiradawidpereira/rust-skills/skills/rust-tests/SKILL.md

rust-tests

Skill0 starsChanged 8 months agoMITView on GitHub

Rust unit and integration test strategy — when to write which kind of test, how to structure test modules, test helpers and builders, error path testing, parameterized tests, integration test organization, binary crate testing, and test isolation. Use when deciding between unit and integration tests, organizing a growing test suite, testing error paths, setting up shared test utilities, or structuring the tests/ directory for a workspace. Also use when tests are slow, brittle, or hard to navigate.

The file

We split this skill into the rules it gives an agent

3 rules. About 0 are common, 2 are distinctive, and 1 is very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈2

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 1

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Unit tests are fast and precise: If a unit test needs a database, a network, or five seconds to run, it is an integration test in disguise.

    line 52
  2. Very specific:

    Error paths need tests too: Every Result::Err variant and every None return should have at least one test.

    line 60
  3. Distinctive:

    Tests own their data: Never depend on test execution order.

    line 77

≈ “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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Testing Strategy

Core Question

What is the right test boundary for this behavior, and how should the test be structured for maintainability?

Unit tests verify internal logic in isolation. Integration tests verify that public API contracts hold when components work together. Choosing wrong leads to either brittle tests that break on refactoring (too-low boundary) or slow, opaque tests that don't pinpoint failures (too-high boundary).


Quick Decisions

SituationReach ForWhy
Pure function with clear inputs/outputsUnit testFast, precise, no setup needed
Private helper with tricky logicUnit test via use super::*Test the logic directly, not through layers of indirection
Public API contractIntegration test in tests/Proves the API works as consumers will use it
Error path or edge caseUnit test per error variantExhaustive, fast, catches regressions early
Complex setup shared across testsTest builder or helper functionDRY without sacrificing clarity
Multiple inputs, same logicMacro-generated parameterized testsCovers variants without copy-paste
Binary crate behaviorIntegration test with CommandTests the actual CLI interface end-to-end
Database or file system interactionIntegration test with isolationReal dependencies need real cleanup strategies
Documenting usage for library consumersDoctestTested documentation stays accurate
Roundtrip or invariant propertiesProperty test (see rust-quality)Finds edge cases humans miss
Mock-heavy test getting complexReconsider the designToo many mocks signals a wrong abstraction boundary

Key Principles

  1. Test behavior, not implementation. Tests that mirror internal structure break on every refactor. Test what the function promises, not how it delivers.
  2. Unit tests are fast and precise. If a unit test needs a database, a network, or five seconds to run, it is an integration test in disguise. Move it.
  3. Integration tests prove contracts. They exercise the public API as a consumer would. Failures here mean you shipped a breaking change.
  4. Error paths need tests too. Every Result::Err variant and every None return should have at least one test. Error paths are where bugs hide because they are where developers look last.
  5. Test names are documentation. parse_rejects_empty_input tells you what broke. test_parse_3 does not. Pattern: function_condition_expected.
  6. Shared setup belongs in helpers, not copy-pasted. Extract builders and factory functions into the test module. Repeated arrange blocks drift apart and obscure the actual test logic.
  7. One assertion per concept. A test can have multiple assert! calls if they verify one logical thing. Unrelated assertions belong in separate tests so failures are specific.
  8. Tests own their data. Never depend on test execution order. Never share mutable state between tests without explicit isolation. Cargo runs tests in parallel by default.

Usage Scenarios

Scenario 1: "I'm adding tests to existing code with no coverage" -> Start with integration tests for the public API to establish a safety net. Then add unit tests for complex internal logic. Integration tests catch the most breakage with the least knowledge of internals. Unit tests follow once you understand the code well enough to identify tricky logic worth testing directly.

Scenario 2: "My test suite is slow and I don't know why" -> Profile with cargo test --lib (unit only) vs cargo test --test '*' (integration only). If unit tests are slow, they are likely touching the filesystem or network — move those to integration tests. If integration tests are slow, look for missing isolation (shared databases, serial locks).

Scenario 3: "I have 50 test functions in one module and it's hard to navigate" -> Nest submodules inside mod tests: group by behavior (mod validation, mod parsing, mod error_cases). Each submodule gets use super::* from the parent test module. Tests become module::tests::validation::rejects_empty_input.

Scenario 4: "I need to test a CLI binary that reads stdin and writes files" -> Use std::process::Command or assert_cmd in integration tests. Create TempDir for file output. Assert on exit code, stdout, and stderr. Never test CLI behavior through library unit tests — test the actual binary.


Reference Index

ReferenceRead When
references/unit-tests.mdWriting unit tests: module organization, private function testing, error paths, test builders, parameterized tests, feature flags, test filtering
references/integration-tests.mdWriting integration tests: tests/ directory structure, shared utilities, binary testing, real dependencies, isolation, snapshots, workspaces

Cross-References

WhenCheck
Property-based testing with proptestrust-quality → Quick Decisions
Mocking with mockall, trait extractionrust-quality → Quick Decisions
Benchmarking with criterionrust-quality → Quick Decisions
Async test patterns (#[tokio::test])rust-async → Quick Decisions
Error type design affecting test assertionsrust-errors → Quick Decisions
Project structure and test file placementrust-architecture → Quick Decisions

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

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.