Overview
TDD tells you how to write a test; a testing strategy decides which kinds of tests, in what proportion, catching which risks, at what cost. The classic answer is the test pyramid — many fast unit tests, fewer integration tests, a thin crust of end-to-end — but the interesting engineering is in its exceptions and its blind spots: contracts between services, properties instead of examples, load, failure injection, and the tests that can only run in production.
Key points
- The pyramid, and why it's shaped that way: cost and feedback speed rise as you climb; flakiness rises too (e2e tests fail for reasons unrelated to the change). The critiques matter: the "testing trophy" argues integration tests catch the most real bugs per line for API/UI code; testcontainers-style real-dependency tests have made "integration" much cheaper than when the pyramid was drawn. The honest rule: test each behavior at the lowest level that can actually falsify it.
- Unit vs. integration is a seam question: sociable units (real collaborators, fake I/O) vs. solitary (mock everything) — over-mocking couples tests to internals and rots them (TDD's London/Detroit trade). Ports from hexagonal architecture are the natural mocking line.
- Contract testing: consumer-driven contracts (Pact-style) verify that provider and consumer agree on the API without spinning both up together — each side tests against the recorded contract in its own pipeline. This is what makes independently deployable microservices honest, and it replaces the integration-environment bottleneck that otherwise reappears.
- Property-based testing: state invariants ("encode∘decode = identity", "sorted output is a permutation of input") and let the framework (QuickCheck lineage: jqwik, Hypothesis, proptest) generate and shrink counterexamples. Finds the edge cases nobody writes examples for; ideal for parsers, serializers, and anything with a round-trip.
- Mutation testing (PIT, Stryker): seed artificial bugs and check the suite catches them — the only honest measure of suite strength; coverage only proves code ran.
- Load & resilience: performance tests belong in the strategy, not in launch week — k6/Gatling with realistic scenarios, percentile assertions, and coordinated-omission awareness (performance engineering); chaos engineering (failure injection, starting in staging with a hypothesis) tests the system's error handling the way unit tests test a function's.
- Testing in production is a tier, not a sin: smoke tests post-deploy, canary analysis, synthetic monitoring — some risks (config, data shape, scale) only exist there; progressive delivery is the safety harness that makes it responsible.
- Suite hygiene is strategy too: flaky tests quarantined and fixed (a suite people rerun is a suite people ignore), test data builders over shared fixtures, deterministic time/randomness injection.
- To explore: approval/snapshot testing (and its rubber-stamp failure mode), fuzzing as security-grade property testing, test impact analysis at scale.
Practice
- Diamond kata, property-based (source) — solve it with properties instead of examples ("symmetric", "widest at the middle") and let the framework shrink the counterexamples.
- Mutation-test a real suite (source) — run PIT (or Stryker) on a project whose suite you trust; every surviving mutant is a test you thought you had.
- Pact contract-testing workshop (source) — consumer-driven contracts between two small services; retire the shared integration environment they replace.
- Build Your Own JSON parser (source) — an ideal property-based target: parse∘serialize round-trips plus a torture suite of malformed inputs.
- Load-test with k6 (source) — script a realistic scenario against a side project with percentile assertions, not averages.
Related
- TDD and BDD — the inner disciplines this strategy arranges.
- CI/CD & delivery engineering — where each tier runs, and the deploy-time tiers.
- Microservice architecture — contract testing as the independence guarantee.
- Performance engineering — the load-test methodology.
- Test-suite scheduling & job partitioning — once the slow suites exist, how they get scheduled and divided.