Skip to content

4 September 2026 · 4 min read

Testing what matters rather than chasing coverage

A coverage percentage tells you almost nothing. Ranking behaviour by what a failure would cost tells you where to spend.

Author
Sawhorse Engineering
Published
4 September 2026

A test suite with ninety percent line coverage and no test for the pricing rules is worse than useless, because it produces confidence that is not warranted. Coverage measures how much code ran, not whether the behaviour is correct.

Start with the risk list

Before writing a test, write the list of things that must never be wrong, in business terms, with the client in the room.

An invoice with the wrong total. A booking on a slot that is not available. A user seeing another customer data. A payment captured twice. Stock sold that does not exist. A permission that lets someone approve their own work.

Rank them by what a failure would cost: money, time, access, data loss, reputation. That ranked list is the test plan. Everything at the top gets full coverage, and low coverage elsewhere is an acceptable, deliberate outcome.

This conversation is also independently valuable. Clients frequently discover during it that two people disagree about what the rule is.

Unit tests for pure logic

Calculations, eligibility rules, state machines, date arithmetic and anything with branching conditions. These run in milliseconds, they are stable, and they are the tests developers will keep passing because they are fast enough not to be an obstacle.

Test the edges rather than the middle. The boundary of a threshold, the empty set, the maximum quantity, the day a season changes, the timezone that crosses a date. Defects live at boundaries.

Integration tests for the gap between query and assumption

Some defects live in the gap between what a query returns and what the code assumed, even when the surrounding calculations are correct.

Run these against a real database with controlled test data. Mocks are useful for isolating behaviour, but they cannot establish that a real query, constraint or transaction behaves as assumed.

This layer covers permission scoping, transaction behaviour under concurrency, constraint violations and the shape of what the database actually returns. It is slower than unit tests and it earns the time.

End to end coverage, deliberately small

A handful of paths that a customer or operator cannot be allowed to lose. Place an order. Book an appointment. Log in and reach the main screen. Submit the form that generates revenue.

Keep it small because these tests are slow and fragile. Keep them stable by using selectors that do not change with copy, waiting on application state rather than a timer, and isolating data per run so a failure means something.

A flaky end to end suite is worse than none, because a red build that is usually a false alarm trains the team to ignore it.

Simulate the failures production supplies

This is the layer most often missing and the one that catches the expensive bugs.

Duplicate webhooks. Out of order events. A third party that times out. A third party that returns a malformed response. A transaction that fails halfway. A deployment that restarts a worker mid job.

Each of these happens in production regularly. A suite that only exercises the happy path has not tested the conditions the system will actually meet.

Accessibility and performance belong in the plan

Automated accessibility checks catch contrast, labelling and structural problems and should run in the pipeline. Keyboard operation and screen reader behaviour are checked by hand, because tooling cannot judge whether a path makes sense.

Performance is measured against seeded production scale data on throttled mobile, not against an empty development database on a fast laptop. A list that is instant with fifty rows and unusable with fifty thousand is a defect that testing at the wrong scale will never find.

What to tell a client about coverage

Not a percentage. A statement of what is covered and what is not.

"Every pricing rule, every permission boundary and every payment path has automated coverage. The administrative reporting screens are covered by manual testing." That is information. Ninety one percent is not.

That tells a future team where the risk sits, which is what they actually need to know.

Prove that a test would detect the defect

A passing test may execute a path without checking its important result. For a booking test, asserting that the response was successful does not establish that only one reservation exists. Query the resulting state and verify the resource, time range, customer and number of reservations.

Temporarily introduce the defect in a controlled branch or test fixture. Remove the uniqueness protection, bypass the tenant filter or return the wrong total. The relevant test should fail for the reason you expect. If it remains green, the assertion is not protecting that behaviour. Restore the correct implementation and keep the stronger assertion.

Concurrency tests need coordination rather than luck. Arrange for two requests to reach the contested operation together, then verify the permitted outcomes. For a single remaining place, one reservation may succeed while the other receives an availability error. Two successful responses are not acceptable merely because the test database eventually contains one row.

Keep external delivery claims separate. A mocked email provider can verify the intended recipient, content and error handling. It cannot prove that a real provider accepted the message or that the inbox received it. Label the evidence accordingly, and use an authorised test address for any real delivery check.

The test report should preserve failed checks as well as successful reruns. A repaired defect with a reproducing test is stronger evidence than a green screenshot with no record of what the suite actually asserted.

Let’s talk about the system you need.

Tell us where the current process breaks down. We can map the workflow, examine the integration requirements and define a practical scope for the build.

  • A mutual NDA is signed before project details are shared.
  • Scope is fixed in writing before any build starts.
  • You own the source code, the IP and the documentation at delivery.
  • Support plans are available and tailored on a discovery or technical call.