Skip to content

Typologies

A typology answers what kind of test this is, which is a different question from where it runs — that is a topology — and from what it is written with, which is tooling.

Keeping the three apart is what stops a strategy from collapsing into a tool list. “An integration test” says nothing about whether it runs against an in-process fake, an ephemeral container or a shared environment, and that is the part that decides both what it costs and what it proves.

Each one exists because it catches something none of the others can. If you cannot name that something, you have a duplicate rather than a level.

Typology The question only it answers Blocks
Unit Is this logic correct — and is this design usable from outside? The merge
Integration Do we talk to that database, broker or API correctly? The merge
Contract Will the other side still understand us after this change? The merge, on both sides
End-to-end Does the critical journey work through the real, assembled stack? The release
Exploratory What did none of us think to write a test for? Nothing — it informs
Performance Does it still hold at the load we promised? The release, against SLOs
Resilience What happens when a dependency misbehaves? The release, for tier-1 services
Security What can someone do here that they should not? The merge, on known CVEs
Accessibility Can everyone actually use it? The merge, on automatable rules

The first four are usually called functional and the next five non-functional. The split is conventional and not very useful: a page nobody can navigate with a keyboard is broken, not sub-optimally decorated, and a checkout that takes eleven seconds does not work.

More useful is the split from the overview: the first five mostly answer have we built the right thing, and the last four mostly answer have we built the thing right.

Each property is checked at the cheapest level that can check it honestly, and at exactly one level.

The expensive failure is not a gap. It is the same validation rule verified in a unit test, an integration test, an end-to-end test and a manual script — four tests that fail together, cost four times as much to update, and produce one bit of information between them.

“Honestly” carries the weight. A unit test with the database mocked cannot honestly check that a query returns the right rows. Pushing it down a level does not make it cheaper; it makes it wrong, and it will pass while production does not.

Two consequences worth stating plainly:

  • When a check moves down a level, delete the one above it. A migration that only adds is a migration that doubles the suite.
  • When two typologies both seem to apply, ask which one fails first on the defect you are worried about. That is the one that owns it.

Start from the property, not the tool.

  1. What could be wrong? Name the defect, specifically. “The discount is miscalculated for orders over 100 euros” — not “discounts”.
  2. What is the cheapest level that would catch it honestly? If it is pure logic, that is a unit test. If it needs a real query planner, integration. If it needs the other team’s deployed service, contract. If it only appears with 200 concurrent users, performance.
  3. Is something already checking it? If yes, stop. If it is checked at a more expensive level, move it down and delete the expensive one.
  4. If nothing can check it before release — it is a candidate for production, which has its own entry conditions.

Step 1 is the one people skip, and skipping it is how a team ends up with a coverage target instead of a strategy.