# Tests
This guide defines language-neutral testing expectations and patterns.
For Python-specific commands and examples, see the
[Python testing guide](tests-python.md).
## Core Testing Principles
- Test observable behavior through public contracts, not private structure.
- Prefer dependency injection and explicit interfaces over global state
mutation.
- Keep tests deterministic and isolated from external mutable systems.
- Design tests to run quickly in normal development loops.
- Target comprehensive line and branch coverage where practical.
- Align tests with capability contracts so they remain stable when internal
organization changes.
## Public Contracts and Internal Visibility
Prefer tests that exercise the same surface callers use in production:
- Call public APIs, CLI entry points, library exports, and other observable
interfaces.
- Assert on return values, errors, side effects, and externally visible
state rather than private helpers or module layout.
- When behavior is hard to reach through the public surface, treat that as a
design signal first. Introduce a narrow dependency-injection seam or
reconsider the boundary before reaching for test-only access.
Do not distort the product API for coverage:
- Do not widen internal visibility, export test-only helpers, or add
documentation-hidden public escape hatches merely so tests can reach
implementation details.
- Do not restructure modules solely to make private names importable from
external test packages when a cleaner seam would preserve the intended
boundary.
Narrow exceptions remain valid:
- Genuinely private algorithms may need focused unit tests when no public
path exercises the same behavior and widening the API would leak
implementation surface.
- Such exceptions should stay small, local, and justified. Prefer a clean
injection point over permanent visibility changes.
- Language-specific overlays describe how to express these exceptions
without turning them into a convenience default. See the
[Rust development guide](practices-rust.md#testing) for Rust
`#[cfg(test)]` policy.
## Anti-Patterns to Avoid
- **Testing against live external services**: Use test doubles for network,
cloud, and other remote dependencies.
- **Over-mocking internal logic**: Excessive mocking can hide real
integration and contract failures.
- **API distortion for tests**: Widening visibility or exporting test-only
helpers solely to reach internals couples tests to private structure and
grows unintended public surface.
- **Hidden global coupling**: Tests should not rely on implicit ordering,
process-global state, or residue from previous tests.
- **Broad exception suppression**: Avoid swallowing failures in tests. Keep
assertions specific and explicit.
## Test Organization
Use a predictable test layout with clear ownership and purpose:
```text
tests/
├── README.md # Project-specific conventions and numbering notes
├── data/ # Fixtures, snapshots, mock payloads
├── unit/ # Fast unit tests around public contracts
├── integration/ # Multi-component and boundary tests
└── smoke/ # High-value, end-to-end sanity checks
```
Recommendations:
- Group tests by public capability rather than private source structure where
possible.
- Keep naming stable and descriptive.
- If your project uses numbered test naming, document the numbering scheme in
`tests/README.md` and keep it current.
## Test Data and Fixtures
- Store reusable fixtures under `tests/data/` with topic-based subdirectories.
- Prefer minimal fixtures scoped to the test purpose.
- Keep snapshot and artifact fixtures reviewable and intentionally versioned.
- Use generated data for high-cardinality cases when static fixtures become
difficult to maintain.
## Validation Workflow
1. During development, run the fastest relevant tests continuously.
2. Before commit, run language-specific quality checks and targeted test suites.
3. Before pull request, run the comprehensive project validation suite.
Use stack-specific overlays for command wrappers and tooling conventions.
## Coverage Strategy
- Treat coverage as a quality signal, not a substitute for thoughtful test
design.
- Cover normal behavior, error behavior, and boundary behavior.
- Use exclusion pragmas only as a last resort and document why they are needed.
## Troubleshooting Approach
When tests are hard to write or unstable:
1. Reassess interface boundaries and dependency injection points.
2. Replace implicit dependencies with explicit constructor/function parameters.
3. Reduce fixture complexity and isolate the failing scenario.
4. Prefer small, composable tests over broad multi-concern tests.
5. Capture the rationale for unavoidable compromises in `tests/README.md`.
## Language-Specific Overlays
- [Python testing guide](tests-python.md) - Python-specific test patterns and
commands.
- [Rust development guide](practices-rust.md#testing) - Rust test layout and
`#[cfg(test)]` boundaries.