Lesson 14 · Senior testing practice

Designing testable code and seams

Why good unit tests are usually a symptom of good decomposition — and why painful tests often reveal boundary problems, not just testing problems.

Your win: explain how seam quality affects test quality, and use test pain as a signal about code structure rather than only a signal about missing mocks.

In plain English Plain English: if code is hard to test honestly, that often means its responsibilities or dependency boundaries are doing too much.

Why this lesson is really about design

Earlier in the course, interfaces and mocks may have felt like testing mechanics. That is a useful first step, but it is not the whole picture. At a senior level, testability becomes one of the clearest windows into design quality.

If a unit is easy to isolate, easy to reason about, and easy to verify with a small test, that usually means its responsibilities are fairly well-shaped. If the test needs too many collaborators, too much setup, too many matchers, or too many weird exceptions, that is often the code telling you something important about itself.

The deeper lesson behind mocks

You can only substitute a collaborator cleanly when the production boundary is already real and understandable. That is what a seam is. A seam is not just “a place where I can inject a mock.” It is a boundary where one responsibility hands off to another responsibility in a way that the codebase can explain.

That is why the best earlier lessons in this course kept connecting test decisions back to production structure. Testability is not a cosmetic property. It is often the shadow cast by decomposition.

The seam rule A good seam does not exist to please the test. It exists because the production boundary is real — and the test becomes simpler as a side effect.

What test pain is trying to tell you

When a test feels painful, the first instinct is often to add more mock setup or another helper layer. Sometimes that is fine. But sometimes the pain is diagnostic.

If one unit needs a long dependency list and complicated control flow just to express one behavior, the better question may be: why does this unit need so much context to do one thing? That question often leads to a more useful fix than another matcher or another helper function.

Backend use case When a new handler or service is hard to test in the repo’s newSuite style, that is often a hint that the unit boundary needs another look before the test gets more elaborate.
Common mistake Solving test pain only by adding more mocks, more helper layers, or more setup machinery without asking whether the production unit itself is too broad.
Read this next

Interfaces as a design seam

Re-read the interface guidance, then map it to the testing seams you have seen in spike.

go.dev — accept interfaces
docs/testing/lessons/0005-test-doubles-why-mock.html

Check yourself (from memory)

Q1. If a unit is consistently painful to test, the best first interpretation is often…

Test pain often reveals design pain, not just tooling pain.
Why is seam quality really a design topic, not just a testing topic?
recall, then click to reveal
Because clear seams reflect real production boundaries and responsibilities; easier testing is the side effect of better decomposition, not the only goal.
Want me to show how to diagnose “too many collaborators” as a decomposition smell? Ask me.

Sources. Go interface guidance; existing testing lessons.