Bad tests ossify

matklad’s How to Test is one of the best articles on testing. 10/10. Almost all of it is right.

My own two cents: No tests beat bad tests.

Bad tests ossify abstractions, turn refactors into fights, slow CI, add flakiness, train people to ignore failures, and hand out false confidence.

Premature tests do this too. When a codebase, its abstractions, and its use cases are still in flux, unit tests freeze the wrong shape in place. The team starts serving the tests instead of the product.

Coding agents don’t fix this; they amplify it. They copy whatever testing patterns the codebase rewards and spread them everywhere.

Coding agents (as of early 2026) struggle when bad tests clash with new logic. They have to decide whether the code is wrong or the test is stale. That call takes taste and judgment current models still lack. And like a human, they can also lose trust in tests and start bypassing them completely.

Put as much care into your testing strategy as into the abstractions it protects. Test debt is easier to ignore than other tech debt. It hurts just as much.

Excerpts

Good code is easy to delete. Tests represent an investment into existing code, and make it costlier to delete (or change).

The solution is to write tests for features in such a way that they are independent of the code. I like to use the neural network test for this:

Neural Network Test

Can you re-use the test suite if your entire software is replaced with an opaque neural network?

Nonetheless, some tests are going to be slow. It pays off to introduce the concept of slow tests early on, arrange the skipping of such tests by default and only exercise them on CI. You don’t need to be fancy, just checking an environment variable at the start of the test is perfectly fine:

One apparent limitation of the style of integrated testing I am describing is checking for properties which are not part of the output. For example, if some kind of caching is involved, you might want to check that the cache is actually being hit, and is not just sitting there. But, by definition, cache is not something that an outside client can observe.

The solution to this problem is to make this extra data a part of the system’s output by adding extra observability points. A good example here is Cargo’s test suite. It is set-up in an integrated, data-driven fashion. Each tests starts with a succinct DSL for setting up a tree of files on disk. Then, a full cargo command is invoked. Finally, the test looks at the command’s output and the resulting state of the file system, and asserts the relevant facts.