Overview
Goal
Build a tiny order-total service the way a real feature would actually get built and verified: a pure calculation TDD'd from a failing test, an external dependency isolated with a stub/mock, an invariant checked by a property test (with a genuine seeded regression caught and shrunk), and the whole thing reached again through a real HTTP endpoint for an integration test -- then read the combined coverage report from the CLI, the way a reviewer actually would.
%% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73, Purple #CC78BC
graph TD
A["Step 1: compute_subtotal#40;#41;<br/>TDD'd unit tests"]:::blue
B["Step 2: TaxGateway<br/>stub/mock isolation"]:::orange
C["Step 3: order-independence<br/>property test #40;Hypothesis + fast-check#41;"]:::teal
D["Step 4: app.py<br/>FastAPI integration test"]:::purple
A --> D
B --> D
C --> D
D --> E["pytest --cov<br/>coverage read from the CLI"]:::blue
classDef blue fill:#0173B2,stroke:#000000,color:#FFFFFF,stroke-width:2px
classDef orange fill:#DE8F05,stroke:#000000,color:#FFFFFF,stroke-width:2px
classDef teal fill:#029E73,stroke:#000000,color:#FFFFFF,stroke-width:2px
classDef purple fill:#CC78BC,stroke:#000000,color:#FFFFFF,stroke-width:2px
Concepts exercised
- co-01: arrange-act-assert, one behavior per test (every unit test below)
- co-05: pytest fixtures for setup (
MagicMock(spec=TaxGateway)construction) - co-12: stub --
TaxGatewayreturns a canned rate, no real external call - co-13: mock -- the stub's call is verified with
assert_called_once_with - co-17: TDD red-then-green -- Step 1's genuine failing-then-passing history
- co-18: property-based testing -- an invariant (order independence), not a hand-picked case
- co-19: shrinking -- Hypothesis reduces a seeded regression to its minimal counterexamples
- co-20: Hypothesis strategies (
st.lists,st.floats) generating the property's inputs - co-21: coverage.py / pytest-cov reading Stmts/Miss/Cover/Missing from a real run
- co-23: integration testing --
app.pycombined withservice.py, both real, unstubbed - co-25:
TestClientdriving a real, tiny FastAPI app end to end
Step 1: service.py -- TDD the pure subtotal function
compute_subtotal() starts life as a naive, not-yet-correct stub (return 0.0). Three unit tests
are written against it FIRST -- two fail immediately, proving the tests actually test something --
then the real implementation replaces the stub and every test goes green.
service.py (complete file, the correct, final version):
"""Capstone: order-total service -- a pure subtotal function plus a tax-gateway-dependent total."""
# This one file underpins all four capstone steps: Step 1 TDDs compute_subtotal() from a failing
# test; Step 2 isolates TaxGateway with a stub/mock; Step 3's property test asserts an invariant
# over compute_order_total(); Step 4's app.py (a sibling file) exposes both over real HTTP.
from __future__ import annotations # => enables modern union/generic syntax under this pinned Python
from abc import ABC, abstractmethod # => co-12: ABC gives TaxGateway a real, enforceable interface # fmt: skip
def compute_subtotal(prices: list[float]) -> float: # => Step 1: TDD'd from a failing test first # fmt: skip
"""Pure function -- sums a list of line-item prices. No IO, no collaborators.""" # => co-01
return round(sum(prices), 2) # => rounds to cents -- the ONLY behavior Step 1's tests pin down # fmt: skip
class TaxGateway(ABC): # => Step 2: the EXTERNAL dependency -- a real implementation would call out # fmt: skip
"""Represents an external tax-rate lookup (e.g. a real HTTP call to a tax API).""" # => co-12
@abstractmethod # => co-12: forces every subclass to implement rate_for_region() -- no silent stub # fmt: skip
def rate_for_region(self, region: str) -> float: # => never implemented HERE -- see RealTaxGateway
raise NotImplementedError # => co-12: the ABSTRACT method body -- callers must subclass this # fmt: skip
class RealTaxGateway(TaxGateway): # => the PRODUCTION implementation -- never touched by any test # fmt: skip
"""The real gateway -- would genuinely call an external tax-rate API. Not used in tests.""" # => co-12
def rate_for_region(self, region: str) -> float: # => the REAL, production-only implementation # fmt: skip
raise NotImplementedError("would call a real external tax-rate API -- never in tests") # => co-12
def compute_order_total(prices: list[float], region: str, tax_gateway: TaxGateway) -> float: # => Step 3
"""Combines the pure subtotal with an EXTERNAL tax rate -- the dependency Step 2 isolates.""" # => co-23
subtotal = compute_subtotal(prices) # => co-01: reuses Step 1's pure function directly # fmt: skip
rate = tax_gateway.rate_for_region(region) # => co-12/co-13: the ISOLATED external call # fmt: skip
return round(subtotal * (1 + rate), 2) # => co-18: the invariant Step 3's property test checks # fmt: skiptest_service_unit.py (Step 1's three TDD tests, plus Step 2's two stub/mock tests -- complete
file):
"""Capstone Step 1 + Step 2: TDD'd unit tests for compute_subtotal(), plus a stubbed TaxGateway."""
# Step 1's three tests below were written BEFORE compute_subtotal() existed (see this page's
# Run block for the genuine red-then-green transcript). Step 2's test isolates
# TaxGateway -- an external dependency -- with a stub, so compute_order_total() runs with NO
# real network call at all.
from __future__ import annotations # => enables modern union/generic syntax under this pinned Python
from unittest.mock import MagicMock # => co-12/co-13: the ONE double type this file's Step 2 uses # fmt: skip
from service import TaxGateway, compute_order_total, compute_subtotal # => co-01: the REAL logic under test # fmt: skip
# ---------------------------------------------------------------------------
# Step 1: TDD unit tests for the PURE function -- red first, then made to pass minimally.
# ---------------------------------------------------------------------------
def test_unit_subtotal_of_empty_list_is_zero() -> None: # => co-17: the FIRST test written, red first # fmt: skip
assert compute_subtotal([]) == 0 # => co-01: the trivial base case -- zero items, zero total # fmt: skip
def test_unit_subtotal_of_single_item() -> None: # => co-17: the SECOND test, red against a stub impl # fmt: skip
assert compute_subtotal([9.99]) == 9.99 # => co-01: one item passes through unchanged # fmt: skip
def test_unit_subtotal_of_multiple_items_rounds_to_cents() -> None: # => co-17: the THIRD test # fmt: skip
assert compute_subtotal([0.10, 0.20]) == 0.30 # => pins down float-rounding behavior too # fmt: skip
# ---------------------------------------------------------------------------
# Step 2: a stub/mock TaxGateway isolates compute_order_total() from any real dependency.
# ---------------------------------------------------------------------------
def test_unit_order_total_uses_stubbed_tax_rate_no_real_gateway_called() -> None: # => co-12
stub_gateway = MagicMock(spec=TaxGateway) # => co-12: spec= constrains it to TaxGateway's shape # fmt: skip
stub_gateway.rate_for_region.return_value = 0.08 # => co-12: a CANNED 8% rate -- no real call # fmt: skip
total = compute_order_total([10.00, 20.00], region="US-CA", tax_gateway=stub_gateway) # => co-01/co-12
assert total == 32.40 # => 30.00 * 1.08, computed WITHOUT any real external tax API call # fmt: skip
stub_gateway.rate_for_region.assert_called_once_with("US-CA") # => co-13: confirms the ISOLATED # fmt: skip
# => dependency WAS consulted (the interaction happened) -- just never for real, over the network
def test_unit_order_total_zero_rate_leaves_subtotal_unchanged() -> None: # => a SECOND stub scenario # fmt: skip
stub_gateway = MagicMock(spec=TaxGateway) # => co-12: a FRESH stub, independent of the test above # fmt: skip
stub_gateway.rate_for_region.return_value = 0.0 # => co-12: a DIFFERENT canned rate, still no real call # fmt: skip
total = compute_order_total([50.00], region="US-OR", tax_gateway=stub_gateway) # => co-01/co-12: combined
assert total == 50.00 # => 0% tax means the subtotal passes through unchanged # fmt: skipVerify (RED): with compute_subtotal() temporarily replaced by return 0.0 (a naive,
not-yet-implemented stub), pytest test_service_unit.py -k "subtotal" -v -p no:warnings
Output (a genuine failure, captured on purpose):
collecting ... collected 5 items / 1 deselected / 4 selected
test_service_unit.py::test_unit_subtotal_of_empty_list_is_zero PASSED [ 25%]
test_service_unit.py::test_unit_subtotal_of_single_item FAILED [ 50%]
test_service_unit.py::test_unit_subtotal_of_multiple_items_rounds_to_cents FAILED [ 75%]
test_service_unit.py::test_unit_order_total_zero_rate_leaves_subtotal_unchanged FAILED [100%]
=================================== FAILURES ===================================
______________________ test_unit_subtotal_of_single_item _______________________
def test_unit_subtotal_of_single_item() -> None: # => co-17: the SECOND test, red against a stub impl # fmt: skip
> assert compute_subtotal([9.99]) == 9.99 # => co-01: one item passes through unchanged # fmt: skip
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E assert 0.0 == 9.99
E + where 0.0 = compute_subtotal([9.99])
test_service_unit.py:23: AssertionError
_____________ test_unit_subtotal_of_multiple_items_rounds_to_cents _____________
def test_unit_subtotal_of_multiple_items_rounds_to_cents() -> None: # => co-17: the THIRD test # fmt: skip
> assert compute_subtotal([0.10, 0.20]) == 0.30 # => pins down float-rounding behavior too # fmt: skip
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E assert 0.0 == 0.3
E + where 0.0 = compute_subtotal([0.1, 0.2])
test_service_unit.py:27: AssertionError
__________ test_unit_order_total_zero_rate_leaves_subtotal_unchanged ___________
def test_unit_order_total_zero_rate_leaves_subtotal_unchanged() -> None: # => a SECOND stub scenario # fmt: skip
stub_gateway = MagicMock(spec=TaxGateway) # => co-12: a FRESH stub, independent of the test above # fmt: skip
stub_gateway.rate_for_region.return_value = 0.0 # => co-12: a DIFFERENT canned rate, still no real call # fmt: skip
total = compute_order_total([50.00], region="US-OR", tax_gateway=stub_gateway) # => co-01/co-12: combined
> assert total == 50.00 # => 0% tax means the subtotal passes through unchanged # fmt: skip
^^^^^^^^^^^^^^^^^^^^^
E assert 0.0 == 50.0
test_service_unit.py:52: AssertionError
=========================== short test summary info ============================
FAILED test_service_unit.py::test_unit_subtotal_of_single_item - assert 0.0 =...
FAILED test_service_unit.py::test_unit_subtotal_of_multiple_items_rounds_to_cents
FAILED test_service_unit.py::test_unit_order_total_zero_rate_leaves_subtotal_unchanged
================== 3 failed, 1 passed, 1 deselected in 0.09s ===================(The -k "subtotal" filter also caught test_unit_order_total_zero_rate_leaves_subtotal_unchanged
by name, an honest side effect of that test's name containing "subtotal" too -- and a genuine third
failure, since it calls compute_order_total(), which calls the still-broken compute_subtotal()
underneath.)
Verify (GREEN): with the real compute_subtotal() restored, pytest test_service_unit.py -v -p no:warnings
Output:
collecting ... collected 5 items
test_service_unit.py::test_unit_subtotal_of_empty_list_is_zero PASSED [ 20%]
test_service_unit.py::test_unit_subtotal_of_single_item PASSED [ 40%]
test_service_unit.py::test_unit_subtotal_of_multiple_items_rounds_to_cents PASSED [ 60%]
test_service_unit.py::test_unit_order_total_uses_stubbed_tax_rate_no_real_gateway_called PASSED [ 80%]
test_service_unit.py::test_unit_order_total_zero_rate_leaves_subtotal_unchanged PASSED [100%]
============================== 5 passed in 0.07s ===============================Acceptance criteria (Step 1): three unit tests for compute_subtotal(), genuinely red against a
stub implementation, genuinely green against the real one; two further unit tests isolate
TaxGateway with MagicMock(spec=TaxGateway), asserting both the computed result (state) and the
call itself (behavior).
Step 2: isolate the external dependency
Already delivered above, inside test_service_unit.py -- TaxGateway is an ABC a real
implementation would call out to an external tax-rate API through; RealTaxGateway.rate_for_region()
deliberately raises if ever actually invoked, so no test can accidentally reach the network. Both
Step 2 tests use MagicMock(spec=TaxGateway), constrained to TaxGateway's own shape, confirming
both the computed total (co-12, state) and that the isolated dependency was genuinely consulted
(co-13, behavior).
Step 3: an order-independence property test, with a caught regression
test_service_property.py (complete file):
"""Capstone Step 3: a Hypothesis property test asserting compute_subtotal() is order-independent."""
# The invariant: summing a list of prices gives the SAME result regardless of the list's
# order -- true for any correct summation, and a genuinely useful property because it holds
# over infinitely many inputs, not just the three hand-picked cases test_service_unit.py checks.
# See the Run block below for this test genuinely CATCHING a seeded regression as TWO distinct
# failures (Hypothesis's shrinking reduces each to its own minimal counterexample), then
# passing again once the regression is reverted.
from __future__ import annotations # => enables modern union/generic syntax under this pinned Python
from hypothesis import given # => co-18: the decorator that turns a plain function into a property # fmt: skip
from hypothesis import strategies as st # => co-20: generates the price lists this property checks # fmt: skip
from service import compute_subtotal # => co-01: the SAME pure function Step 1's unit tests checked # fmt: skip
@given( # => co-20: Hypothesis GENERATES many price lists -- not three hand-picked examples # fmt: skip
prices=st.lists(
st.floats(min_value=0, max_value=1000, allow_nan=False, allow_infinity=False), # => co-20: bounded, finite floats # fmt: skip
min_size=0, # => co-20: includes the empty-list edge case in every run # fmt: skip
max_size=10, # => co-20: caps list size -- keeps each generated case fast to check # fmt: skip
)
)
def test_property_subtotal_is_order_independent(prices: list[float]) -> None: # => co-18: the invariant
forward = compute_subtotal(prices) # => the ORIGINAL order # fmt: skip
reversed_total = compute_subtotal(list(reversed(prices))) # => the SAME items, REVERSED order # fmt: skip
assert forward == reversed_total # => co-18/co-19: true for a CORRECT sum, for EVERY generated list # fmt: skipservice.property.test.ts (the TS/fast-check sibling -- prose-referenced per this capstone's
Python-primary scope, but genuinely run standalone in an isolated scratch environment against
fast-check 4.9.0 + Vitest 4.1.10 during authoring; neither version is wired into this repo's own
apps/ayokoding-www, which pins Vitest at 4.1.0 and does not declare fast-check as a dependency
-- run npm install fast-check first to try this file here):
// Capstone Step 3 (TS half): the SAME order-independence invariant, checked with fast-check.
//
// Mirrors test_service_property.py's Hypothesis test -- computeSubtotal() must return the
// SAME total regardless of the price list's order, checked over MANY fast-check-generated
// lists rather than a few hand-picked ones. Run standalone with `npx vitest run` (verified
// against fast-check 4.9.0 + Vitest 4.1.10 in an isolated scratch env, not this repo's own
// dependency pins -- run `npm install fast-check` first to try this file here).
import { describe, expect, it } from "vitest";
import fc from "fast-check";
function computeSubtotal(prices: number[]): number {
// => the SAME correct logic as service.py's compute_subtotal() -- order-independent by construction
const total = prices.reduce((sum, price) => sum + price, 0);
return Math.round(total * 100) / 100; // => rounds to cents, matching Python's round(x, 2) behavior
}
describe("computeSubtotal order independence", () => {
it("returns the same total regardless of price order", () => {
fc.assert(
// => co-18/co-20 (TS side): fast-check GENERATES many price arrays, not hand-picked ones
fc.property(fc.array(fc.float({ min: 0, max: 1000, noNaN: true }), { maxLength: 10 }), (prices) => {
const forward = computeSubtotal(prices); // => the ORIGINAL order
const reversedTotal = computeSubtotal([...prices].reverse()); // => the SAME items, REVERSED
expect(forward).toBeCloseTo(reversedTotal, 2); // => co-18: the invariant, checked every run
}),
);
});
});Verify (seeded regression): compute_subtotal() temporarily replaced with a bug that
double-counts the first price (return round(prices[0] * 2 + sum(prices[1:]), 2)), then
pytest test_service_property.py -v -p no:warnings
Output (a genuine failure, captured on purpose; the sandbox's absolute temp-directory prefix in
the traceback's File "..." lines is normalized below to bare filenames, matching this topic's
standard preamble-trimming policy -- every remaining line is verbatim):
collecting ... collected 1 item
test_service_property.py::test_property_subtotal_is_order_independent FAILED [100%]
=================================== FAILURES ===================================
_________________ test_property_subtotal_is_order_independent __________________
+ Exception Group Traceback (most recent call last):
| File "test_service_property.py", line 18, in test_property_subtotal_is_order_independent
| prices=st.lists(
| ^^^
| File "hypothesis/core.py", line 2274, in wrapped_test
| raise the_error_hypothesis_found
| ExceptionGroup: Hypothesis found 2 distinct failures. (2 sub-exceptions)
+-+---------------- 1 ----------------
| Traceback (most recent call last):
| File "test_service_property.py", line 27, in test_property_subtotal_is_order_independent
| assert forward == reversed_total # => co-18/co-19: true for a CORRECT sum, for EVERY generated list # fmt: skip
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| AssertionError: assert 1.0 == 2.0
| Falsifying example: test_property_subtotal_is_order_independent(
| prices=[0.0, 1.0],
| )
+---------------- 2 ----------------
| Traceback (most recent call last):
| File "test_service_property.py", line 25, in test_property_subtotal_is_order_independent
| forward = compute_subtotal(prices) # => the ORIGINAL order # fmt: skip
| File "service.py", line 13, in compute_subtotal
| return round(prices[0] * 2 + sum(prices[1:]), 2)
| ~~~~~~^^^
| IndexError: list index out of range
| Falsifying example: test_property_subtotal_is_order_independent(
| prices=[],
| )
+------------------------------------
================================== Hypothesis ==================================
`git apply .hypothesis/patches/2026-07-15--9c653fb9.patch` to add failing examples to your code.
=========================== short test summary info ============================
FAILED test_service_property.py::test_property_subtotal_is_order_independent
============================== 1 failed in 0.43s ===============================Hypothesis genuinely finds two distinct falsifying examples for this one seeded bug, not one:
the order-mismatch shrinks to the minimal 2-item list [0.0, 1.0] (co-18/co-19, the property's own
invariant), and a second, independent failure mode -- prices[0] raising IndexError whenever the
list is empty, since min_size=0 includes the empty list in every run -- shrinks to the minimal
empty list []. Both are genuine consequences of the SAME one-line bug; Hypothesis's default
ExceptionGroup reporting surfaces both distinct failures rather than stopping at the first one
found.
Verify (reverted): the correct compute_subtotal() restored, same command.
Output:
collecting ... collected 1 item
test_service_property.py::test_property_subtotal_is_order_independent PASSED [100%]
============================== 1 passed in 0.14s ===============================Output (TS half): npx vitest run service.property.test.ts
RUN v4.1.10
Test Files 1 passed (1)
Tests 1 passed (1)
Duration 135ms (transform 19ms, setup 0ms, import 36ms, tests 4ms, environment 0ms)Acceptance criteria (Step 3): an order-independence property genuinely catches the seeded
bug as two distinct failure modes, with Hypothesis's shrinking reducing each to its own minimal
counterexample (the order-mismatch to [0.0, 1.0], the empty-list crash to []) rather than a
large, hard-to-read generated list -- and genuinely passes again once the regression is reverted;
the TS/fast-check sibling test independently confirms the same invariant against the equivalent
TypeScript logic.
Step 4: app.py -- reach the same logic through a real HTTP endpoint
app.py (complete file):
"""Capstone: a tiny 2-endpoint FastAPI app -- Step 4's integration test drives this over HTTP."""
# Standing in for this plan's "Backend-Essentials" app: an in-memory order service with just
# enough surface (add an item, read the total) to give Step 4 a real HTTP boundary to test
# through, built directly on the SAME service.py logic Steps 1-3 already verified in isolation.
from __future__ import annotations # => enables modern union/generic syntax under this pinned Python
from fastapi import FastAPI # => co-25: the REAL, if tiny, ASGI app Step 4 drives with TestClient # fmt: skip
from pydantic import BaseModel # => co-23: validates every POST body against the Item shape below # fmt: skip
from service import RealTaxGateway, compute_order_total # => co-01: reuses Steps 1-3's SAME logic # fmt: skip
app = FastAPI() # => the WHOLE small system Step 4's integration test drives, end to end # fmt: skip
_ORDERS: dict[str, list[float]] = {} # => the app's own in-memory state, real across requests # fmt: skip
class Item(BaseModel): # => the shape a caller POSTs to add a line item # fmt: skip
price: float # => co-23: Pydantic REJECTS a request body that doesn't match this field # fmt: skip
@app.post("/orders/{order_id}/items") # => endpoint 1 of 2 -- adds one line item # fmt: skip
def add_item(order_id: str, item: Item) -> dict[str, object]: # => co-25: the REAL route handler # fmt: skip
_ORDERS.setdefault(order_id, []).append(item.price) # => genuine, cumulative in-memory state # fmt: skip
return {"order_id": order_id, "item_count": len(_ORDERS[order_id])} # => co-25: real response body
@app.get("/orders/{order_id}/total") # => endpoint 2 of 2 -- reads the computed total # fmt: skip
def get_total(order_id: str) -> dict[str, object]: # => co-25: the SECOND real route handler # fmt: skip
prices = _ORDERS.get(order_id, []) # => co-25: an EMPTY list for an order that never existed # fmt: skip
# region="US-OR" (Oregon has NO state sales tax) sidesteps needing a REAL external tax
# call for this demo app -- RealTaxGateway.rate_for_region() would raise if it were ever
# actually invoked, but 0% regions never reach that line in THIS deliberately tiny app.
total = compute_order_total(prices, region="US-OR", tax_gateway=_ZeroRateGateway()) # => co-01/co-23 # fmt: skip
return {"order_id": order_id, "total": total} # => co-25: the SAME logic Steps 1-3 verified alone # fmt: skip
class _ZeroRateGateway(RealTaxGateway): # => a tiny, HONEST override -- avoids a NotImplementedError # fmt: skip
"""A trivial always-zero gateway for this demo app's one hardcoded, tax-free region.""" # => co-12
def rate_for_region(self, region: str) -> float: # => co-12: genuinely returns 0, not a mock # fmt: skip
return 0.0 # => co-12: a REAL, honest override -- not a MagicMock standing in for this # fmt: skiptest_service_integration.py (complete file):
"""Capstone Step 4: an integration test hitting app.py's real HTTP endpoints with TestClient."""
# Drives the SAME service.py logic Steps 1-3 already verified in isolation, now reached through
# a real FastAPI app and a real (in-process) HTTP boundary -- exactly the integration tier this
# capstone's pyramid needed. See the Run block below for the coverage report read from
# `pytest --cov` across ALL FOUR test files together.
from __future__ import annotations # => enables modern union/generic syntax under this pinned Python
from app import app # => co-23: the REAL, running FastAPI app -- not a mock of it # fmt: skip
from fastapi.testclient import TestClient # => co-23: drives the real app in-process, no real socket # fmt: skip
client = TestClient(app) # => co-23: drives the REAL, running app -- no mocking of app.py itself # fmt: skip
def test_integration_add_item_then_read_total() -> None: # => co-23: the full HTTP round trip # fmt: skip
add_response = client.post("/orders/order-cap-1/items", json={"price": 12.50}) # => a REAL POST # fmt: skip
assert add_response.status_code == 200 # => co-23: the REAL handler's status code # fmt: skip
assert add_response.json() == {"order_id": "order-cap-1", "item_count": 1} # => co-23: real body # fmt: skip
add_second = client.post("/orders/order-cap-1/items", json={"price": 7.50}) # => a SECOND real POST # fmt: skip
assert add_second.status_code == 200 # => co-23: still a real, successful response # fmt: skip
assert add_second.json()["item_count"] == 2 # => co-23: state accumulates ACROSS real requests # fmt: skip
total_response = client.get("/orders/order-cap-1/total") # => a REAL GET, reading real state # fmt: skip
assert total_response.status_code == 200 # => co-23: the real read succeeded # fmt: skip
assert total_response.json() == {"order_id": "order-cap-1", "total": 20.0} # => 12.50 + 7.50 # fmt: skip
def test_integration_total_for_unknown_order_is_zero() -> None: # => co-23: a REAL empty-state path # fmt: skip
response = client.get("/orders/never-created/total") # => an order id that genuinely never existed # fmt: skip
assert response.status_code == 200 # => co-23: still a real, successful (not 404) response # fmt: skip
assert response.json() == {"order_id": "never-created", "total": 0} # => co-23: genuinely empty # fmt: skipVerify: pytest test_service_integration.py -v -p no:warnings
Output:
collecting ... collected 2 items
test_service_integration.py::test_integration_add_item_then_read_total PASSED [ 50%]
test_service_integration.py::test_integration_total_for_unknown_order_is_zero PASSED [100%]
============================== 2 passed in 0.27s ===============================Acceptance criteria (Step 4): two integration tests drive app.py's real FastAPI endpoints
through TestClient, confirming both cumulative in-memory state across requests and a genuine
empty-state path for an order that was never created -- no part of app.py or service.py is
mocked anywhere in this step.
Read the combined coverage report
Run: pytest . --cov=service --cov=app --cov-report=term-missing -v -p no:warnings (all four
test files together: test_service_unit.py, test_service_property.py,
test_service_integration.py, plus the standalone TS file above)
Output:
collecting ... collected 8 items
test_service_integration.py::test_integration_add_item_then_read_total PASSED [ 12%]
test_service_integration.py::test_integration_total_for_unknown_order_is_zero PASSED [ 25%]
test_service_property.py::test_property_subtotal_is_order_independent PASSED [ 37%]
test_service_unit.py::test_unit_subtotal_of_empty_list_is_zero PASSED [ 50%]
test_service_unit.py::test_unit_subtotal_of_single_item PASSED [ 62%]
test_service_unit.py::test_unit_subtotal_of_multiple_items_rounds_to_cents PASSED [ 75%]
test_service_unit.py::test_unit_order_total_uses_stubbed_tax_rate_no_real_gateway_called PASSED [ 87%]
test_service_unit.py::test_unit_order_total_zero_rate_leaves_subtotal_unchanged PASSED [100%]
================================ tests coverage ================================
______________ coverage: platform darwin, python 3.13.12-final-0 _______________
Name Stmts Miss Cover Missing
------------------------------------------
app.py 20 0 100%
service.py 15 2 87% 21, 28
------------------------------------------
TOTAL 35 2 94%
============================== 8 passed in 0.57s ===============================Missing 21, 28 names the two lines no test ever reaches: TaxGateway.rate_for_region()'s own
raise NotImplementedError (line 21) and RealTaxGateway.rate_for_region()'s raise NotImplementedError(...) (line 28) -- both deliberately unreachable, since RealTaxGateway is
never instantiated by any test in this capstone (app.py uses the local _ZeroRateGateway
override instead, and test_service_unit.py's stubs replace TaxGateway entirely with
MagicMock(spec=TaxGateway)). Reading those two missing lines this way -- confirming WHY they are
uncovered, not just that they are -- is exactly co-27's "read the report" skill this whole tutorial
builds toward.
Acceptance criteria
compute_subtotal()has a genuine red-then-green TDD history: three tests written and run against a naive stub first (3 real failures captured), then against the correct implementation (5 passed).TaxGatewayis isolated fromcompute_order_total()withMagicMock(spec=TaxGateway)in two tests, each asserting both the computed result and that the isolated dependency was called.- The order-independence property test genuinely catches the seeded regression as two distinct
failures (shrunk to
prices=[0.0, 1.0]and toprices=[]) and genuinely passes again once reverted; the TS/fast-check sibling independently confirms the same invariant. app.py's two endpoints are driven by a realTestClientintest_service_integration.py, confirming cumulative state and a genuine empty-order path.- The full four-file suite passes together (
8 passed), andpytest --covreportsapp.pyat 100%,service.pyat 87% (with the two missing lines named and explained), TOTAL 94%.
Done bar
This capstone is DONE when the RED transcript above shows real, non-fabricated failures against the
naive stub; the GREEN transcript shows all five unit tests passing against the real implementation;
the property test's seeded-regression transcript shows a real Hypothesis-shrunk counterexample; the
integration transcript shows both endpoints genuinely exercised through TestClient; and the final
coverage table is read directly from a real pytest --cov run, with every uncovered line named and
explained rather than silently ignored.
← Previous: BDD & Executable Specifications · Next: Drilling →
Last updated July 14, 2026