testing
Testing patterns for AAA structure, naming, mocks versus real dependencies, coverage targets, TDD flow, parameterized tests, and flake prevention. Use when writing, reviewing, or repairing automated t
SKILL.mdskills/testing/SKILL.md
GitHubTesting
Write tests that isolate behavior, document intent, and fail for one reason at a time.
When to Activate
- Add tests for new behavior
- Refactor a flaky or slow test suite
- Decide whether a collaborator should be mocked
- Raise or enforce coverage expectations
- Convert duplicated tests into parameterized cases
- Review a PR for missing test coverage
- Drive a change with TDD
Test Structure
| Phase | Purpose | Rule |
|---|---|---|
| Arrange | Build inputs and collaborators | Keep setup minimal and explicit |
| Act | Execute one behavior | One action per test |
| Assert | Verify outcome | Assert observable effects, not internals |
Naming and Coverage
| Style | Example |
|---|---|
| Python | test_create_user_rejects_duplicate_email |
| TypeScript | it("rejects duplicate email on createUser") |
| Go | func TestCreateUser_RejectsDuplicateEmail(t *testing.T) |
| Codebase Type | Target |
|---|---|
| Shared library | >= 90% line coverage |
| Backend service | >= 80% line coverage with integration coverage on critical flows |
| Frontend app | >= 70% on logic-heavy modules, plus E2E coverage on core journeys |
Mock vs Real
| Dependency | Default | Reason |
|---|---|---|
| Database adapter in unit tests | Mock or in-memory fake | Keep units fast |
| Pure domain services | Real | No need to mock deterministic logic |
| HTTP clients | Mock at network boundary | Avoid external flake |
| Serialization and validation | Real | Contract drift matters |
TDD and Parameterization
| Step | Goal |
|---|---|
| Red | Prove the behavior is missing |
| Green | Implement the smallest passing change |
| Refactor | Improve structure without changing behavior |
Python
@pytest.mark.parametrize(("input", "expected"), [("A", "a"), ("B", "b")])
def test_normalize_code(input: str, expected: str) -> None:
assert normalize_code(input) == expected
TypeScript
it.each([
["A", "a"],
["B", "b"],
])("normalizes %s", (input, expected) => {
expect(normalizeCode(input)).toBe(expected);
});
Flakiness Prevention
| Risk | Prevention |
|---|---|
| Time dependence | Freeze time or inject clock |
| Randomness | Seed generators |
| Order dependence | Isolate global state and DB fixtures |
| Network | Stub external calls |
BAD
def test_user_flow():
# creates user, sends email, checks DB, checks cache
...
GOOD
def test_create_user_persists_record() -> None:
...
def test_create_user_enqueues_welcome_email() -> None:
...
Checklist
- Each test follows Arrange, Act, Assert
- Test names describe behavior and scenario
- Mocks are used only at unstable boundaries
- Critical paths have integration coverage
- Time, randomness, and network are controlled
- Parameterized tests replace duplicated assertion logic where useful
- Coverage targets match the codebase type