BOOK DEEP READ · CONTINUOUS DELIVERY · CH 8
Continuous Delivery · Ch 8 · Jez Humble & David Farley · 2010
You tap a button, pay, and a confirmation message arrives — someone has to check that the whole chain actually works. The previous chapter's gate only inspected parts: every small piece looks fine on its own. This chapter is about the next gate: assemble the whole machine, run it end to end, and see whether the thing the user actually wanted got built.
Think of the final walkthrough on a new apartment. Testing the concrete mix and measuring the rebar — that's parts inspection. The walkthrough is something else: you open every tap to see if water comes out, flip every switch to see if the light turns on, close every door to see if it locks. Every part can pass and the toilet still won't flush, and then nobody can live there.
Here's the counterintuitive bit: the walkthrough checklist is most valuable not on move-in day, but before construction starts. The moment you write it down, both sides discover that "enclosed balcony" meant two different things. Fixing that now costs one sentence; fixing it after the walls are up means knocking walls down.
This job used to be done by people: a room full of them working through a checklist by hand. The trouble is that every change means, in principle, redoing the entire checklist — you touched the payment button, so who can promise signup still works? A manual pass takes days, so the team ships less often; shipping less often piles up more changes, and more changes make trouble more likely. That's the same loop from two chapters ago.
People also get tired doing repetitive checks. They skip. They think "it was fine last time, it's probably fine now." The most tedious work is exactly the work least suited to humans.
First, write the checklist as something a machine can run — in the language of the business. "A new customer places an order; once payment succeeds, they should receive a confirmation email." That one sentence is both the requirement and the test. Writing it with the customer, the analyst, the developer and the tester in the room is where the vague bits surface.
Second — and this is the real craft of the chapter — separate "what to check" from "how to click it." The thousands of checklist items only say "log in, place an order, pay." One separate layer underneath is the only place that knows where the login button lives and what it's called. Move the button and you change one line in that bottom layer; the thousands of items above don't change at all. Teams whose acceptance tests grow too expensive to keep almost always skipped this cut.
Third, if you can avoid going through the screen, avoid it. Driving the screen is slow and gets tripped by tiny layout tweaks. Talking to the system directly, behind the screen, does the same job ten times faster and far more steadily.
The main payoff is that people dare to change the code: rerun the whole business flow, see all green, and you'll press the release button. Its real identity isn't "a bug-finding tool" — it's a safety net that makes people willing to move.
It does have a cost: checks like these are inherently slow and expensive, tens of minutes a round at best, so they must always be the minority — most checking belongs at the cheaper gate before this one.
Acceptance tests don't answer "is the code correct?" — they answer "did the thing the user asked for actually get built?" And whether you can afford to keep them depends entirely on whether you separated what to check from how to click it.
Want the actual layering, why record-and-playback is a trap, and whether thousands of end-to-end tests can really run? → Switch to the deep read
An acceptance test doesn't ask "is the code right?" — it asks "was the value the user wanted actually delivered?" The counterintuitive part: this chapter is nominally about testing and substantially about requirements. Its single largest payoff arrives before any code is written, in the hour where analysts, testers, developers and the customer turn "what does done mean" into executable acceptance criteria. And whether the suite survives three months hinges on exactly one thing: did you split "what to verify" from "how to drive it"? If not, thousands of tests get flattened by every cosmetic UI tweak and the team quietly abandons them. If yes, the same thousands run for a decade.
placeOrder(customerA, 100).This chapter follows Ch7 inside Part II. Ch5 drew the whole conveyor belt from commit to production, Ch7 zoomed into the first slot (the commit stage), and this chapter zooms into the second — the gate an artifact passes through once the commit stage goes green. Upstream, it inherits Ch4's testing quadrants, taking over the "business-facing, supporting the team" square; downstream, Ch9 picks up nonfunctional acceptance (capacity, security) and Ch12 expands the test-data problem this chapter only touches. In today's world it maps to Cucumber / FitNesse / Concordion, to Selenium and its descendants WebDriver, Playwright and Cypress, and to the "as production-like as possible" gamma environment in a cloud pipeline.
A green commit stage only says "this change didn't obviously break anything." It says nothing about whether the user got what they asked for — unit tests are written by developers for themselves, and they verify that the code behaves the way the developer intended, not that the intention was right. That gap is where the most expensive class of defect lives: the feature is finished, the code has no bugs, and it isn't what the customer wanted.
The old world plugged that gap with manual acceptance testing. Do the arithmetic and you see why it collapses: a mid-sized business system has roughly 150~250 primary scenarios, and a full manual regression pass typically costs 2 testers 2~3 days. At 12 releases a year that's 50~70 person-days; but continuous delivery aims at being releasable at any time, hundreds of times a year — at that frequency manual regression isn't expensive, it's arithmetically impossible. Worse is how it fails: attention on the fifth repetition is nothing like attention on the first. Manual regression is exactly the kind of work that gets less reliable the more you need it to be reliable.
The chapter also has to answer a hard objection, which the authors put on the table in the opening pages: "acceptance tests are too expensive, you can't maintain them." Their reply isn't denial, it's concession — badly written acceptance tests really are unaffordable — followed by a whole chapter on how to write them so they aren't. Which means the actual subject of this chapter is maintainability, not coverage.
The chapter's first and most-skipped claim: acceptance criteria must be written before work on the requirement begins, and written jointly by analysts, testers, developers and the customer — they are simultaneously the specification, the sign-off checklist, and the skeleton of the eventual automated test. Why insist on "before"? Because writing them is an intense requirements-clarification exercise. The moment you have to turn "support coupons" into "given a spend-100-get-20-off coupon, when the customer places a 95 order, then the coupon is unavailable and the message says 5 short," the ambiguity surfaces. Caught before work starts, the fix costs a sentence; caught after the feature is built, it costs rework. This chapter's biggest single payoff lands before any test code exists.
Ownership follows from that: the chapter explicitly rejects outsourcing acceptance tests to a separate QA team — if the people writing the code aren't accountable for green, red becomes somebody else's problem and then accumulates. This is the same discipline as Ch3's "stop the line." Testers' value moves accordingly: away from repetitive manual clicking, toward defining acceptance criteria and doing exploratory testing — neither of which a machine can do.
If you remember one diagram from this chapter, make it this one. Acceptance tests split into three layers (the fourth is the system itself):
The rule lives in the wall between them: layers 1 and 2 may only use business and domain vocabulary, while "click the element with id btn-submit" may appear only in layer 3, and only once. The payoff is quantifiable: change the login page from "username + password" to "phone number + one-time code" and, without layering, every test that logs in first has to change — typically over 80% of the suite; with layering the change lands on the single login() method in the driver, and not one line above it moves. The maintenance cost of an acceptance suite is almost entirely determined by how many times UI detail was duplicated.
The chapter's second key judgement reads like a contradiction on first pass: acceptance tests must exercise the whole deployed system, but the entry point should sit below the UI — driving the application through the public API that the UI itself calls.
The reason is speed multiplied by brittleness. A UI-driven test typically takes 3~15 seconds (waiting on rendering and async loads); the same scenario driven through the public API usually lands at 100~300 milliseconds — one to two orders of magnitude. Worse is the brittleness: one frontend framework upgrade or layout change can turn a batch of tests red while the business logic is entirely intact.
So who tests the UI itself? The chapter's position: if the UI contains business logic, the first thing to do is push that logic out of the UI, leaving a small number of UI tests over the purely presentational remainder. The advice fits today's frontend era even better than 2010 — modern frontends carry a lot of logic, and the answer is still to make it independently testable rather than stacking thousands of end-to-end tests on a browser.
The pitch is seductive: no code, click once and you have a test. The chapter firmly rejects it as the mainstay of acceptance testing, on three solid grounds:
The chapter does allow one honest exception: for a legacy system with no tests at all that you must start changing, weaving a coarse net with capture-replay is a reasonable stopgap — but it's scaffolding, not foundation.
The chapter is quite prescriptive about the shape of this stage:
On duration, the chapter gives no hard number like the commit stage's ten minutes, only a relative constraint: the acceptance stage may be slow, but it must be short enough to run many times a day. The engineering answer is parallelism — shard by scenario across machines, so the total is the slowest shard rather than the sum of all shards. How far that road goes is answered by the public LMAX numbers below.
A unit test can pretend the world doesn't exist; an acceptance test can't — it runs against a real system with a database and history. State is the source of every weird acceptance failure: the username already exists on the second run, an order left behind by the previous test pollutes a total, two parallel tests fight over the same account. The chapter offers three postures and states a preference:
user-7f3a91). The cost is needing a way to create data; the benefit is that tests can run in any order and in parallel — and parallelism is the only way out of "slow," so these are really one decision.The headline anti-pattern is a shared "big fat test database" for the whole team: fast to start with, then rotting — nobody remembers which row belongs to whom, nobody dares delete anything, and eventually it becomes a legacy asset of its own. Ch12 covers test data properly.
Running against a real system means facing three real-system problems:
sleep calls sprinkled everywhere. A fixed wait is either wasted time (slowing the whole suite) or too short (a flaky test); it loses both ways.Table 1 · Which layer should a given acceptance test drive?
| Entry point | Per test | Covers | Brittleness | When to pick it |
|---|---|---|---|---|
| Through the UI | 3~15 s | Whole chain incl. frontend logic and rendering | Highest: layout, ids or timing change and it's red | Keep a few primary paths when the UI genuinely holds logic |
| Public API below the UI | 100~300 ms | The complete business chain except the UI | Low | The chapter's workhorse entry point |
| Message / event interface | tens of ms ~ s | Async and event-driven paths | Medium: must handle waiting and out-of-order | The natural entry for event-driven systems |
| Domain objects directly | ms | One slice of logic, no integration | Minimal | This is a unit test — it belongs to the commit stage (Ch7) |
Table 2 · How to make acceptance criteria executable
| Approach | Who can read it | Maintenance | Cost / risk |
|---|---|---|---|
| Team-built DSL | Developers + business (with guidance) | Low | You must build and maintain that language; slow to start, cheapest long term |
| Given/When/Then tools (Cucumber, FitNesse) | Business reads it directly | Medium | If the business never actually reads it, you've added a layer of glue nobody looks at |
| Plain test framework (plus a driver layer) | Developers only | Medium-low | Loses the executable-specification value; degenerates into a regression net |
| Capture-replay | Nobody | Very high | Welded to UI detail, can't be written first, unreadable; only fit as legacy scaffolding |
Table 3 · What to do about state and test data
| Strategy | How | Upside | Cost |
|---|---|---|---|
| Test isolation | Each test creates its own data under unique keys | Any order, full parallelism | Needs a data-creation path and a cleanup policy |
| Adaptive tests | Assert relative change, not absolute values | Works in environments carrying existing data | Weaker assertions, more convoluted code |
| Test sequencing | Fixed order, each test eats the previous state | Easy to write | No parallelism; one failure cascades — not recommended |
| Shared "big fat" test DB | One preloaded dataset for the whole team | Fastest start | Cross-contamination, rots over time, nobody dares delete anything |
Table 4 · The acceptance stage got too slow — now what?
| Move | Effect | Cost / precondition |
|---|---|---|
| Shard by scenario across machines | Near-linear speedup, the single most effective move | Requires real test isolation; costs hardware (see the LMAX numbers below) |
| Move the entry point from UI to API | Commonly one to two orders of magnitude | Gives up UI coverage; keep a few UI tests separately |
| Stub slow, unstable external systems | Removes the longest waits | Stubs drift from reality; needs integration tests as backstop |
| Share expensive one-time setup | Avoids repeated environment startup | Sharing is coupling; slides quietly back into ordering dependencies |
| Delete redundantly covered scenarios | Often surprisingly large wins | Look at data; don't delete on instinct |
| Just run it nightly instead | Looks like the easy way out | Feedback drops from minutes to days — you're back in the Ch1 world |
Table 5 · Which gate does a given check belong to?
| What you want to verify | Where | Why |
|---|---|---|
| Boundary cases of a pricing function | Commit stage (Ch7) | Pure logic, verifiable in milliseconds without starting the system |
| Order → payment → confirmation | Acceptance stage | The business value only exists when the whole system is wired together |
| Is the environment alive? | Deployment (smoke) test | Runs before acceptance so environment faults are triaged at a glance |
| Capacity / performance / security | Separate nonfunctional stage (Ch9) | Needs baselines and dedicated environments; different cadence |
| Layout and visual design | A few UI tests plus humans | The worst return on automation investment |
| "Is this feature any good?" | Exploratory testing by people | Machines can't answer this — it's what testers are freed up to do |
One judgement underlies all five tables: every minute of the acceptance stage is expensive, so buy only what genuinely requires the whole system to be wired together to prove. If the commit stage can settle it in milliseconds, keep it out; if a machine can't answer it, don't force one to.
This chapter's claims are all default practice today, under other names: the driver layer grew into the industry-standard Page Object pattern; Given/When/Then grew into the Cucumber / SpecFlow / Behave family; Selenium's WebDriver interface became a W3C standard, with Playwright and Cypress travelling the same road; and "production-like environment plus smoke first" became the pre-production environment and post-deployment health check that ship as standard in every cloud pipeline.
What's most worth arguing about today, though, is the fight this chapter started: how many end-to-end acceptance tests should you have? Both sides left public, checkable numbers.
This is a frequent interview and design-review topic. Asked "how do you do end-to-end testing," the answer that scores isn't a list of tools, it's four facts: do you have a separate driver layer (how many tests change when the UI changes)? How long does a full run take, and at what parallelism? What share of failures are environmental, and how are they triaged apart from real defects? And what happens to the flaky ones — quarantine with a deadline, or unlimited retries?
① Acceptance tests verify that business value was delivered, not that the code is logically correct; they take the user's point of view against the whole deployed system.
② Nominally about testing, substantially about requirements: acceptance criteria must be defined before work starts, jointly by analysts, testers, developers and the customer. The biggest payoff lands before any test code is written.
③ The core craft is layering: acceptance criteria (business language) / test implementation (domain actions) / application driver (the only layer that knows UI and API detail). A UI change touches layer three only; the thousands above don't move.
④ Enter through the public API just below the UI: per-test cost drops from seconds to ~100ms and layout changes stop breaking things. If the UI holds logic, push the logic out of the UI.
⑤ Don't build on record-and-playback: it needs the app to exist first, welds UI detail into every script, and produces something nobody can read. It's only fit as legacy scaffolding.
⑥ The stage's shape: one artifact → deployed by production's own scripts into a production-like environment → smoke first → acceptance in parallel → green promotes; as a bonus your deployment process is rehearsed many times a day.
⑦ The commonest failure at this stage is environmental, not a business defect — the whole value of smoke tests is triaging the two apart at a glance.
⑧ State is the dirtiest part: prefer test isolation (own data, unique keys), because it is what unlocks parallelism — and parallelism is the only cure for slowness.
⑨ Handle asynchrony with polling plus timeout, never fixed sleep; make time controllable; stub external systems but keep honest books and back them with integration tests.
⑩ How much end-to-end testing is genuinely contested: Google recommends 70/20/10 and quantified "bigger means flakier," while LMAX squeezed 11,000 end-to-end tests into under 20 minutes. The dividing line is the DSL and driver layer — without them, this chapter's approach becomes the very disaster it warns about.