Overview — Test One Feature End to End
Every previous Part taught one idea in isolation: how a tester thinks, how to design cases, what the levels of testing are, what coverage measures, how CI enforces it, how to file a bug. That is a lot of tools laid out on a bench. This Part picks up one small feature and uses all of them on it, in order, until the feature is genuinely trustworthy.
We do not add a single new concept here. Instead we answer the question the whole book has been circling — how do you earn justified confidence that a piece of software works, and how do you reason about the edge cases that break it? — by doing it, once, completely, on something you can hold in your head.
The one feature under test
Section titled “The one feature under test”The feature is Snip, a URL shortener with exactly two HTTP endpoints:
POST /shorten request: { "url": "https://example.com/some/very/long/path" } response: { "code": "a3Xk9", "short": "https://sn.ip/a3Xk9" }
GET /:code GET /a3Xk9 -> 302 Found, Location: https://example.com/some/very/long/path GET /zzzzz -> 404 Not Found (unknown code)That is the entire application. You create a short code for a long URL, and later you hand back the long URL when someone visits the code. A mapping in a store; a lookup; a redirect.
Under those two endpoints sit three moving parts, and every one of them is a place tests will need to reach:
POST /shorten { url } │ ▼ ┌──────────────┐ generate ┌──────────────┐ write ┌──────────┐ │ validate │──────────────▶│ code maker │───────────▶│ store │ │ the url │ short code │ (alphabet, │ code->url │ (db / │ └──────────────┘ │ length) │ │ memory) │ └──────────────┘ └────┬─────┘ │ read GET /:code ──────────── look up code ────────────────────────▶│ │ │ ▼ 302 + Location: <original url> OR 404 ◀───────┘Validate, generate, store, look up, redirect. Five verbs — and each verb is a small pile of assumptions waiting to be wrong.
It is deliberately small and boring. We are not here to admire the app — a URL shortener is a solved problem. We chose something you already understand so that all of your attention goes to the testing, not to decoding business logic. When the app is trivial, the interesting question stops being “what does it do?” and becomes “what could break, and how would I know it didn’t?” That question is the entire lesson.
The goal, in one sentence
Section titled “The goal, in one sentence”By the end of this Part, we will have earned justified confidence that Snip’s two endpoints work — and we will be able to name, and defend against, the edge cases that break them.
“Justified” is the load-bearing word. Confidence you can justify is confidence backed by artifacts: a risk list, a set of designed cases, real passing tests at three levels, a coverage number you can defend, a CI gate that keeps them honest, and — because real testing always turns something up — one genuine bug, written down properly.
The throughline for this Part
Section titled “The throughline for this Part”Every artifact we produce on the coming pages answers the same two-part question:
┌─────────────────────────────┐ │ What could break? │ <- risk analysis, edge cases, test design └──────────────┬──────────────┘ │ ▼ ┌─────────────────────────────┐ │ How do we know it doesn't? │ <- unit / integration / e2e tests, coverage, CI └─────────────────────────────┘The left half is imagination: adversarial, pessimistic, “what would a hostile input, a slow network, or a duplicate request do to this?” The right half is evidence: an executable test that fails loudly when the answer is bad. A test plan is just a disciplined pairing of the two — for each way the feature could break, a specific check that proves it doesn’t. Keep that pairing in mind and every page below has an obvious place in the whole.
The single most important habit this Part is trying to build is refusing to let those two halves drift apart. A risk with no test behind it is a hope; a test with no risk behind it is cargo-cult effort — green ticks that reassure you without ever having been in danger of failing for a reason you cared about. Every artifact we build keeps a risk and its evidence on the same line:
RISK (what could break) EVIDENCE (how we know it doesn't) ----------------------------------- ------------------------------------ unknown code returns 500, not 404 e2e test: GET /zzzzz -> assert 404 same url shortened twice -> 2 codes integration test: shorten x2 -> assert one code OR documented policy generated code can collide unit test: force collision -> retry, no duplicate mapping written empty url field is accepted unit test: POST {"url":""} -> 400That table is not decoration. By the time we reach CI, that table is the test plan — every row a risk we named, every row a test that would fail if the risk came true.
Roadmap for this Part
Section titled “Roadmap for this Part”Read these in order. Each one takes the feature one step further, and each one draws directly on an earlier Part of the book so you can go back and re-read the theory the moment you feel a gap.
| # | Page | What it produces | Draws on |
|---|---|---|---|
| 2 | Map the Risks and Edge Cases | A ranked list of what could break in Snip | The tester’s mindset; edge cases and boundaries |
| 3 | Design the Test Cases | A concrete test plan: inputs, expected outputs, why each exists | Test design techniques (equivalence, boundary, decision tables) |
| 4 | Write the Unit and Integration Tests | Real passing tests for the code-generation logic and the store | Test levels: unit vs. integration |
| 5 | End-to-End Tests and a Coverage Goal | Black-box tests over the running API, plus a defended coverage target | End-to-end testing; coverage and what it does and doesn’t mean |
| 6 | Wire the Tests into CI | A pipeline that runs every test on every change and blocks bad merges | Testing in practice: continuous integration |
| 7 | File the Bug You Found | One reproducible, well-written bug report | The anatomy of a good bug report |
| — | Revision — The Whole Book on One Feature | A prose recap tying every Part back to Snip | Everything above |
By the time you reach page 7 you will have, sitting in front of you: a copy-pasteable test plan, a suite of real passing tests at the unit, integration, and end-to-end levels, a coverage number you can defend, a CI configuration that enforces all of it, and one filed bug that the testing process genuinely surfaced. Not a description of those things — the things themselves.
Notice the shape of the roadmap, because it mirrors how good testing actually flows:
pages 2-3 pages 4-5 page 6 page 7 ───────────── ───────────── ────────── ────────── THINK BUILD ENFORCE REPORT what could break? tests at 3 levels CI gate the bug (no code yet) + coverage number (permanent) testing found │ │ │ │ └── risk half ────────┴─── evidence half ─┴────────────────┘The first two pages write no code at all — they are pure thinking about failure. The middle two turn that thinking into executable tests at three levels of the pyramid. Page 6 makes those tests permanent so they cannot silently rot. And page 7 shows what testing is ultimately for: one of the risks we mapped turns out to be a real defect, and we do the last honest thing — we write it down so someone can fix it once and for good.
Why the whole book collapses onto this
Section titled “Why the whole book collapses onto this”Each earlier Part answered a general question. This Part forces every one of those general answers to become a specific one, about Snip:
- The tester’s mindset stops being an attitude and becomes a written list: the exact ways
POST /shortenandGET /:codecan misbehave. - Test design stops being techniques on a slide and becomes named cases with concrete inputs and expected outputs.
- Test levels stop being a pyramid diagram and become three real files that run at three real speeds.
- Coverage stops being a percentage argument and becomes one number you chose on purpose and can defend.
- CI stops being “you should automate tests” and becomes a config file that fails a pull request.
- The bug report stops being a template and becomes an actual defect you can reproduce on demand.
That is the point of a capstone: theory you can recite is not the same as theory you can apply. Applying it once, end to end, is what converts the one into the other.
Under the hood — why one feature, not one app
Section titled “Under the hood — why one feature, not one app”You might expect a capstone to build the whole system: many features, a real database, auth, a front end. We deliberately do the opposite, and the reason is about how testing skill actually transfers.
The hard, transferable part of testing is not typing assertions. It is the loop — look at a feature, enumerate what could break, choose the few cases that carry the risk, place each at the right level, run them cheaply on every change, and act on what they find. That loop is identical whether the feature is a URL shortener or a payments ledger. If we ran it across fifty features, you would learn it once and then watch us repeat it forty-nine times. Running it once, slowly and completely, on a feature small enough to fully hold in your head, teaches the loop with nothing else competing for attention.
the loop, once: what it teaches (transfers everywhere): ----------------------- -------------------------------------------- enumerate risks -> adversarial, pessimistic reading of a spec choose cases -> equivalence + boundary thinking under a budget place at a level -> unit vs integration vs e2e trade-offs run on every change -> CI as a ratchet that never loosens act on findings -> a bug report precise enough to be fixed onceScale is a multiplier applied after you have the loop. Get the loop right on Snip and it is the same loop, larger, on anything.
The thread
Section titled “The thread”Hold onto one sentence as you read the rest of this Part: every artifact we produce answers “what could break, and how do we know it doesn’t?” The risk list is the first half of that question written down. Every test file is the second half made executable. Coverage tells you how much of the code your second-half answers actually touched. CI keeps the answers true over time. The bug report is what happens when honest testing finds a gap between the two halves — a way it could break that we did not yet know it doesn’t.
If a page ever feels like busywork, ask which half of the question its artifact serves. If you cannot say, you have found either a missing test or a missing risk — and that is a useful thing to have found.
What this Part is not
Section titled “What this Part is not”To keep expectations honest, a few things this capstone deliberately leaves out:
- It is not exhaustive. We will not test all seventy-something input combinations. The whole skill is choosing the dozen or two that carry the risk and justifying the ones we skip — testing is triage, not enumeration.
- It is not a proof of correctness. Passing tests and a coverage number are strong evidence, not a guarantee. We will be explicit about what our tests do and do not prove, especially when the coverage page tempts us to read too much into a percentage.
- It is not tool-worship. The code samples use ordinary, common tooling, but the reasoning is language- and framework-agnostic. The loop matters; the runner is a detail. (As of mid-2026 the specific tools churn; the questions do not.)
- It is not finished when the tests pass. The last page is the bug, on purpose. A test suite whose only output is “all green” has usually stopped looking hard enough. Real testing ends with something to fix.
Hold those four caveats and you will read every following page for what it actually claims, not more.
Start with the first half: Map the Risks and Edge Cases.
Check your understanding
Section titled “Check your understanding”- State the goal of this Part in one sentence, and explain what the word “justified” adds to “confidence.”
- Snip is deliberately trivial. What is the reason for choosing a boring feature, and how does it change the question you spend your attention on?
- The Part has a two-part throughline. What are the two halves of the question, and which testing artifacts belong to each half?
- Pick any row of the roadmap table. Name the artifact that page produces and the earlier Part it draws on.
- The “By the numbers” box argues that test effort scales with something other than lines of code. What does it scale with, and why does that matter for a 40-line feature?
Show answers
- By the end we will have earned justified confidence that Snip’s two endpoints work, and be able to name and defend against the edge cases that break them. “Justified” means the confidence is backed by artifacts you can point to — a risk list, designed cases, passing tests at three levels, a defended coverage number, a CI gate, and one filed bug — rather than a feeling that “it probably works.”
- The app is not the lesson; the testing is. A feature you already understand lets all of your attention go to “what could break, and how do we know it doesn’t?” instead of to decoding business logic. The question shifts from “what does it do?” to “how could it fail, and how would I prove it doesn’t?”
- The two halves are “what could break?” and “how do we know it doesn’t?” The first half is served by the risk map, edge-case analysis, and test design; the second half is served by the unit, integration, and end-to-end tests, plus coverage and CI that keep those answers honest.
- Any row is acceptable. Example: End-to-End Tests and a Coverage Goal produces black-box tests over the running API and a defended coverage target, drawing on the Parts about end-to-end testing and coverage.
- It scales with the number of distinct behaviors and edge cases, not with lines of code. That matters because even a tiny 40-line feature can hide a dozen independent ways to fail (duplicate URLs, unknown codes, hostile schemes, concurrency collisions), so “it’s only 40 lines” is never a reason to skip testing.