Skip to content

Revision — The Whole Book on One Feature

This Part did not teach a new idea. It took every idea the book had already laid on the bench and used all of them, in order, on one deliberately boring feature — Snip, a URL shortener with two endpoints, POST /shorten and GET /:code. We started with a fuzzy feeling that “it probably works” and ended with something firmer: justified confidence, backed by artifacts you can point at. This page walks the arc back from end to start, so the whole loop sits in your head as one motion rather than seven separate pages.

The goal was one sentence, and it is worth restating before we recap how we reached it: by the end we would have earned justified confidence that Snip’s two endpoints work, and be able to name and defend against the edge cases that break them. Every page below was in service of that single claim.

Before we take the pages one at a time, here is the whole journey as a single chain. Notice that each link feeds the next, and that the very first link — a risk — is the one that every later artifact ultimately traces back to.

Map the risks ── what could break in Snip? ──► a ranked risk register
Design the test cases ── one case per risk ─────────► a concrete test plan
Unit + integration ── prove the small pieces ────► fast, real, passing tests
E2E + coverage ── prove the whole, measured ─► black-box tests + a defended %
Wire into CI ── keep the answers honest ───► a gate that blocks bad merges
File the bug ── the gap testing surfaced ──► one reproducible bug report

Read top to bottom, that is the entire discipline. You imagine failures, you turn each imagined failure into a specific check, you run those checks at the cheapest level that can catch each one, you measure how much of the code your checks actually touched, you make a machine enforce all of it forever, and — because honest testing always finds something — you write down the one real defect it surfaced.

The risk register came first, and it came first on purpose. We did not open an editor and start typing asserts. We sat with Snip’s tiny surface — one URL string in, one short code out, two storage operations — and asked, methodically, what can break here? Empty URLs, non-URL garbage, dangerous schemes a browser would later follow, 50,000-character monsters, Unicode that must round-trip byte-for-byte, the same URL shortened twice, two requests colliding on the same code under concurrency, an unknown code on lookup. None of that came from inspiration. It came from walking the inputs to their boundaries, which is a procedure anyone can repeat.

That register is the root of everything that followed. Its job was to make the rest of the Part non-arbitrary: from here on, no test exists without a risk behind it, and no risk we could name is allowed to ship untested.

Designing the test cases turned the register from prose into a plan. Each risk became one or more named cases with a concrete input and an expected output, chosen with the design techniques from earlier in the book — equivalence classes so we did not test the same behavior a hundred redundant times, boundary values because bugs cluster one byte on each side of a limit, a decision table where the rules interacted. The plan was still just text, but it was text with a spine: read any row and you could say which risk it defends against, and read any risk and you could find the row that pins it down.

This is the trace that makes the whole capstone trustworthy. A test plan derived from a risk register cannot contain a case that tests nothing important, and cannot omit a case for something important — because both would show up as a mismatch between the two lists.

Three levels, each catching what it is cheapest to catch

Section titled “Three levels, each catching what it is cheapest to catch”

Then the plan became executable, at three levels, and the levels were not an accident of taste. The unit and integration tests proved the small pieces in isolation and in combination: the code-generation logic (is the code the right length, the right alphabet, collision-resistant?) as fast unit tests, and the store round-trip (write a mapping, read it back, get the exact bytes) as integration tests that touch a real database. The end-to-end tests then drove the running API from the outside — POST /shorten, take the returned code, GET it, follow the 302, confirm you land on the original URL — proving the wired-together whole in a way no isolated test can.

Each level earned its place by catching a class of bug the others cannot see cheaply. Unit tests catch logic errors in milliseconds. Integration tests catch the seam between the logic and the store. End-to-end tests catch the failures that only appear when every real component is present — the routing, the serialization, the redirect. The pyramid shape (many fast tests, fewer slow ones) was a cost decision, not a diagram to memorize.

Coverage measured the gap, and CI froze the answer

Section titled “Coverage measured the gap, and CI froze the answer”

Coverage gave us one honest number and a caveat we stated out loud: coverage tells you which lines your tests executed, never whether your assertions were meaningful. We chose a target we could defend for this feature rather than chasing 100% as a vanity figure, and we used the uncovered lines as a to-do list — each gap either got a test or an explicit “not worth it, here’s why.”

Wiring into CI then made the whole suite run on every change and block any merge that broke it. This is the step that converts a one-time act of testing into a durable property. Without it, the risk register and the passing suite decay the first time someone is in a hurry. With it, the answer to “does Snip still work?” is recomputed automatically on every commit, and a regression cannot reach the main branch quietly.

Finally, the bug report. Real testing that never finds anything is usually testing that was not looking hard enough. Ours found a genuine defect — a gap between a way Snip could break and our prior belief that it didn’t — and we wrote it up the way a good bug report demands: a title that states the symptom, exact reproduction steps, observed versus expected behavior, and enough environment detail that someone else can reproduce it on demand. That report is not paperwork; it is the artifact that turns a discovery into a fix someone can act on.

Underneath all seven pages was one claim, and it is the claim the whole book has been building toward: justified confidence comes from deliberately hunting the edge cases that break software, not from running the happy path.

Running the happy path proves only that the feature works when nothing goes wrong — and nothing going wrong is the one scenario you never needed a test for, because you would have noticed it in thirty seconds by hand. Every artifact in this capstone was aimed at the other scenarios: the empty input, the hostile scheme, the duplicate request, the concurrent collision, the code that does not exist. Confidence is the shadow those tests cast. Skip the edge cases and the shadow is an illusion — the feature “works” only in the demo.

That is why “justified” was the load-bearing word from the very first page. Anyone can feel confident. Justified confidence is confidence you can defend, because behind it sits a risk register, a derived test plan, passing tests at three levels, a coverage number you chose on purpose, a CI gate that keeps it all true, and one filed bug proving the process was actually adversarial and not decorative.

It is worth naming the property that makes this loop trustworthy rather than merely busy: every artifact traces back to a risk. Follow any test in the suite backward and you reach a case in the plan; follow that case backward and you reach a line in the risk register; follow that line backward and you reach a boundary you walked on Snip’s tiny input surface. There are no orphan tests testing nothing that matters, and — because the plan was derived from the register rather than invented alongside it — there are no named risks quietly missing a test.

That two-way trace is the answer to the two questions that ran through the whole book. “What could break?” is the risk register. “How do we know it doesn’t?” is the executable suite. Coverage measures how much of the second answer the first question actually reached. CI keeps both answers true over time. The bug report is what you file when the two ever fall out of sync — a way it could break that you did not yet know it doesn’t.

Snip was the excuse, not the lesson. Nothing in the seven pages depended on the feature being a URL shortener; the loop is the same for a payment endpoint, a login form, a data pipeline, or a firmware routine on a rocket:

risks ─► cases ─► tests (cheapest level first) ─► coverage ─► CI ─► file what you find
▲ │
└────────────── a filed bug is a new risk; feed it back ◄──────────────┘

Scale changes the size of each box, never the shape of the loop. A large feature has a longer risk register and more test files; it still starts with “what could break?” and still ends with a machine that enforces the answer and a place to write down what slipped through. Learn the loop once, at capstone scale, and you own it at every scale.

And it is genuinely a loop, not a line. The bug you filed on the last page is really a new entry for the risk register — a failure mode you now know about, which earns a test, which the CI gate then guards forever. Run the loop long enough and your risk register becomes the memory of every way this software has ever tried to break.

You now have more than a finished exercise; you have a reusable method and the tools to run it. The tester’s mindset and the edge-case field guide are your what could break? instrument. The design techniques, the level split, the coverage caveat, the CI pattern, and the bug-report anatomy are your how do we know it doesn’t? toolkit. None of them were specific to Snip.

So point them at your own code. Pick one feature you actually own — however small — and run the exact loop: walk its inputs to their boundaries and write the risk register, derive a test case for each risk, implement those tests at the cheapest level that can catch each one, measure the coverage and defend the number, wire the suite into CI so a regression cannot merge quietly, and file the first bug the process surfaces. Do that once on real code and the method stops being something you read about and becomes something you have. That is the only difference between knowing how testing works and being able to earn justified confidence on demand — and it is the whole reason this book existed.