Test strategy
Skill nimadorostkar/Claude-Skills-collection/skills/testing/test-strategy
A curated library of 137 production-grade skills for Claude and other AI coding agents.
npx -y skills add nimadorostkar/Claude-Skills-collection --skill test-strategyAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- 23 days oldThe repository was created 23 days ago. New is not bad, but a brand new repository carrying a familiar-sounding name is the shape a typosquat arrives in, and there has been no time for anyone else to find a problem with it.
- 23 stars23 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Use when deciding what to test and at which level. Covers the test pyramid, what belongs in unit versus integration versus end-to-end tests, coverage as a signal rather than a target, and eliminating flakiness.
SKILL.md
5.4 KB, as published. Nobody here has run it
Test Strategy
Purpose
Build a test suite that catches real defects, runs fast enough to be run, and does not need to be rewritten every time the code is refactored.
When to Use
- Designing a test suite for a new project.
- A suite that is slow, flaky, or fails to catch bugs.
- Deciding what to test for a specific change.
- Setting a coverage policy without making coverage the goal.
Capabilities
- Test-level selection: unit, integration, contract, end-to-end.
- Test-double strategy: what to fake, what to use for real.
- Coverage interpretation.
- Flakiness diagnosis and elimination.
- Suite performance: parallelization and selective execution.
Inputs
- The system, its dependencies, and its risk profile.
- The current suite: its runtime, its failure rate, its bug-escape rate.
Outputs
- A test suite whose distribution across levels is deliberate.
- A fast feedback loop for the tests run on every change.
- Zero tolerated flakes.
Workflow
- Test behavior, not implementation — A test that breaks when you rename a private method is a test that prevents refactoring rather than enabling it.
- Choose the level by what you are verifying — Business logic: unit tests, fast and many. Integration with a real database or queue: integration tests, fewer. A complete user journey: end-to-end, a handful.
- Use real dependencies where practical — A test against a real Postgres in a container catches the SQL error that a mocked repository never will. Mocks verify that you called a thing; they do not verify that it works.
- Write the test that would have caught the bug — After every production defect. This is where the highest-value tests come from — a real bug is empirical evidence of an untested path.
- Treat a flake as a defect — Quarantine it immediately, then fix it. A suite with a 2% flake rate teaches the team to re-run and eventually to ignore.
- Keep the fast loop fast — Under five minutes for what runs on every change, or people will stop running it.
Best Practices
- Coverage is a signal, not a target. 100% coverage with assertion-free tests catches nothing; 60% coverage on the paths that matter catches most things. Look at what is not covered, not at the number.
- The most valuable tests are at the boundaries: the empty list, the null, the concurrent write, the network failure. The happy path is the one that already works.
- Mock what you do not own (a third-party payment API); use the real thing for what you do (your own database). Mocking your own repository layer tests only that your mock is configured correctly.
- A test that requires a comment to explain what it is testing is testing too much.
- Never assert on a log message or an internal call count unless that is the contract. Those assertions break on every refactor and catch nothing.
- Delete tests that no longer earn their keep. A test suite is code and carries maintenance cost.
Examples
A test that verifies behavior, and one that verifies implementation:
# Bad: verifies the implementation. Breaks on any refactor; catches no bugs.
def test_refund_calls_gateway():
gateway = Mock()
RefundService(gateway).refund(order, 1000)
gateway.refund.assert_called_once_with(order.charge_id, 1000)
# This passes even if the refund is never recorded, the amount is wrong
# in the database, and the customer is charged again.
# Good: verifies the behavior that the user and the business care about.
def test_refund_reduces_balance_and_is_idempotent(db, fake_gateway):
order = place_order(db, total_cents=5_000)
service = RefundService(fake_gateway, db)
result = service.refund(order.id, amount_cents=2_000)
assert result.ok
assert db.orders.get(order.id).refunded_cents == 2_000
assert fake_gateway.total_refunded(order.charge_id) == 2_000
# The same request again must not refund twice.
service.refund(order.id, amount_cents=2_000, idempotency_key=result.key)
assert db.orders.get(order.id).refunded_cents == 2_000
The distribution that actually works:
Unit ~70% milliseconds each business logic, edge cases, error paths
Integration ~25% seconds each real DB, real queue, real HTTP layer
End-to-end ~5% tens of seconds the three journeys that must never break
The proportions matter less than the principle: put the volume where the tests
are fast and the coverage is cheap, and reserve the slow, brittle level for
the handful of paths whose failure would be catastrophic.
Notes
- Testcontainers gives you a real database, queue, or cache per test run, in Docker. It has largely eliminated the argument for mocking your own infrastructure — the real thing is now nearly as easy.
- Contract tests (Pact and similar) verify that two services agree on their interface without deploying both. They are the right tool when end-to-end tests across services have become the bottleneck.
- A test suite that has never caught a bug in production code is either a very good sign or a very bad one. Check which by mutating the code and seeing whether the tests notice.