This repository should follow a simple rule:
Ship behavior with tests, not hope.
The goal is not to maximize test count. The goal is to maximize confidence while keeping feedback fast.
Use the testing pyramid:
- 70% unit tests — pure logic, fast feedback, no network/DB/filesystem
- 20% integration tests — module boundaries, DB, queues, APIs, CLI boundaries
- 10% end-to-end tests — real user flows, critical paths only
If E2E tests start to outnumber unit tests, the pyramid is upside down.
Use Red → Green → Refactor for all new business logic and bug fixes.
Write a failing test that describes the expected behavior.
Write the minimum code needed to make the test pass.
Clean up names, structure, duplication, and complexity while keeping tests green.
Every bug fix should include:
- a regression test that fails before the fix
- the implementation change
- all tests green after the fix
- pure functions
- parsers and validators
- business rules
- edge cases and boundary values
- error handling branches
- serialization / transformation logic
- DB repositories and migrations
- HTTP handlers and API contracts
- message queues / cron jobs / workers
- file IO boundaries
- third-party integrations using fakes, mocks, or test environments
- login / auth
- checkout / payment / subscription
- create / update / delete core entities
- import / export / sync workflows
- deployment smoke checks
Structure tests as:
- Arrange
- Act
- Assert
Do not assert private/internal implementation details unless absolutely necessary.
A test must give the same result every run. Avoid:
- random state without a seed
- real wall-clock timing assumptions
- network calls in unit tests
- shared mutable state between tests
Mock:
- external APIs
- DB in unit tests
- filesystem
- time / clock
- environment variables when needed
Do not mock core domain logic just to make tests pass.
Use retries, polling, explicit waits, or event-based assertions.
Small focused tests fail more clearly and are easier to maintain.
If the same behavior must be checked across many inputs, use table-driven / parameterized tests.
At minimum CI should block merges on:
- test failures
- syntax / compile failures
- formatting drift if formatter is enforced
- linter failures for agreed rules
Coverage is a signal, not the goal.
Recommended targets:
- 80%+ line coverage for core business logic
- 90%+ branch coverage for critical logic (auth, payments, permissions, pricing)
- 100% coverage is not required if it creates low-value tests
Never write pointless tests just to raise the number.
- New features should include tests.
- Bug fixes should include regression tests.
- Flaky tests must be fixed or removed quickly.
- Skipped tests must have a linked issue or clear reason.
- Test data should be explicit and readable.
- Tests should run locally and in CI.
- Prefer fast tests close to the code.
Use the closest idiomatic version for the stack in this repo.
src/
module_x/
...
module_x.test.* # unit tests close to source when idiomatic
tests/
integration/
e2e/
fixtures/
helpers/
- Prefer
pytest - Use
@pytest.mark.parametrizefor variants - Use fixtures for setup, not giant inline setup blocks
- Keep unit tests away from live external services
- Use
cargo test - Put small unit tests near the code with
#[cfg(test)] - Put integration tests in
tests/ - Prefer small deterministic tests over giant golden-path suites
- Prefer Vitest or Jest for unit/integration
- Prefer Playwright for E2E
- Mock HTTP/network boundaries, not internal functions
- Prefer PHPUnit
- Test services and domain classes directly
- Keep controller tests thin and integration-oriented
- Prefer JUnit 5
- Use table-driven patterns where possible
- Keep Spring / framework-heavy tests for integration level only
Every repository should aim to have these jobs where relevant:
- format / lint
- unit tests
- integration tests
- build / package
- smoke or E2E for critical apps only
Recommended order:
- fast checks first
- expensive checks later
- fail fast on syntax / compile / formatter issues
- testing implementation instead of behavior
- giant brittle E2E suites
- hidden magic test setup
- copy-pasted tests
- asserting logs instead of outcomes
- real credentials in tests
- non-isolated tests
- ignored flaky failures in CI
- merging broken test suites
A change is not done until:
- code works
- tests prove it
- CI is green
- the next person can understand the intent
This guide is distilled from established testing practice:
- testing pyramid
- TDD / red-green-refactor
- unit / integration / E2E separation
- deterministic and maintainable CI-friendly tests
It was added as a repository-wide baseline so future changes converge on one testing standard.