Tests that prove behavior and docs that stay current
A coding agent writes tests quickly, and they pass. That tells you the tests agree with the code, and it doesn’t tell you they would notice if the code were wrong. In this lesson you break a small program on purpose and run the tests an agent wrote for it. Some of them still pass, and you rewrite one of them so it checks what the program must do. Then you review a change the agent made to the same program, and find the docs it left out of date.
The program is shop/, a pricing module for a small web shop, in the
course repository. It adds up the lines of an order and adds shipping:
4.95 euros (EUR), or nothing for an order of 50.00 EUR or more.
FREE_SHIPPING_FROM = 50.00SHIPPING = 4.95
def shipping(amount): """Return the shipping cost for an order with this subtotal.""" if amount >= FREE_SHIPPING_FROM: return 0.0 return SHIPPINGBreak the code on purpose
Section titled “Break the code on purpose”Testing with an agent means keeping tests that prove what the code does for its users, and reviewing the tests the agent writes as closely as the code. Agents produce tests readily, and some of those tests can pass because they repeat what the code happens to do. The Academy course The AI-native SDLC playbook (SDLC is short for the development life cycle of software) argues that when an agent produces most of a team’s code, review and testing become the slow steps [1]. A quick way to review a test is to make the code wrong and see whether the test notices.
From the root of your clone of the course repository, copy the shop to a place of your own, and run its tests there.
cp -R site/examples/coding-with-agents/tests-and-docs-that-hold/shop ~/shopcd ~/shoppython3 test_pricing.py; echo "exit status $?"The agent's tests, before any change
Section titled “The agent's tests, before any change”Run the tests in your copy and compare what you see with the output below.
$ python3 test_pricing.py test_subtotal_adds_the_lines: pass test_small_order_pays_shipping: pass test_large_order_ships_free: pass test_total_adds_shipping: pass test_total_of_a_known_order: pass 5 passed, 0 failed exit status 0
Output verified in CI from site/examples/coding-with-agents/tests-and-docs-that-hold/run_tests.py.
The examples in this lesson aren’t graded. Each one is a command you run, with output the course checks on every build.
The tests are in test_pricing.py. ORDER is two items
at 12.50 EUR and one at 20.00 EUR.
ORDER = [(2, 12.50), (1, 20.00)]
def test_subtotal_adds_the_lines(): assert subtotal(ORDER) == 45.00, subtotal(ORDER)
def test_small_order_pays_shipping(): assert shipping(10.00) == SHIPPING, shipping(10.00)
def test_large_order_ships_free(): assert shipping(FREE_SHIPPING_FROM + 10) == 0.0, shipping(FREE_SHIPPING_FROM + 10)
def test_total_adds_shipping(): expected = subtotal(ORDER) + shipping(subtotal(ORDER)) assert total(ORDER) == expected, total(ORDER)
def test_total_of_a_known_order(): assert total(ORDER) == 49.95, total(ORDER)Now make a bug a customer would see on every small order. Open
pricing.py in your copy and change SHIPPING = 4.95 to
SHIPPING = 5.95. Before you run the tests again, decide which ones
still pass.
Which tests miss the wrong price?
Section titled “Which tests miss the wrong price?”A pricing module charges 4.95 EUR shipping below 50.00 EUR, from a constant SHIPPING. Its tests: the subtotal of a fixed order equals 45.00; shipping(10.00) equals SHIPPING; shipping(FREE_SHIPPING_FROM + 10) equals 0.0; total(ORDER) equals subtotal plus shipping of the subtotal; total(ORDER) equals 49.95.
The constant now holds 5.95 where the shop charges 4.95. Which of the five tests still pass?
For each test, where does the expected value come from: the test, or the code it tests?
The tests, with the wrong price
Section titled “The tests, with the wrong price”Run the tests in your copy again. The course’s script makes the same
change to a fresh copy and runs the tests. You can run it from
site/examples/coding-with-agents/tests-and-docs-that-hold/ in your
clone.
python3 break_price.py$ python3 test_pricing.py test_subtotal_adds_the_lines: pass test_small_order_pays_shipping: pass test_large_order_ships_free: pass test_total_adds_shipping: pass test_total_of_a_known_order: FAIL: got 50.95 4 passed, 1 failed exit status 1
Output verified in CI from site/examples/coding-with-agents/tests-and-docs-that-hold/break_price.py.
One test out of five caught a wrong price. Two of the tests that passed
never looked at the price, and that’s fine: a test for the subtotal
doesn’t need to. The other two mirror the implementation.
test_small_order_pays_shipping compares the shipping cost with
SHIPPING, the same constant the code returns, so it passes whatever
the constant holds. test_total_adds_shipping works out the expected
total by calling the same functions that total calls. A test like
this repeats the code, and a test that repeats the code agrees with it
when the code is wrong.
The fix is to write down, in the test, the value the program must
produce, worked out by hand or taken from the spec. Rewrite
test_small_order_pays_shipping in your copy so it states the price
the shop charges:
def test_small_order_pays_shipping(): assert shipping(10.00) == 4.95, shipping(10.00)The rewritten test, with the wrong price
Section titled “The rewritten test, with the wrong price”Run the tests with the wrong price still in pricing.py. The course’s
script makes both changes to a fresh copy:
python3 rewrite_test.py$ python3 test_pricing.py test_subtotal_adds_the_lines: pass test_small_order_pays_shipping: FAIL: got 5.95 test_large_order_ships_free: pass test_total_adds_shipping: pass test_total_of_a_known_order: FAIL: got 50.95 3 passed, 2 failed exit status 1
Output verified in CI from site/examples/coding-with-agents/tests-and-docs-that-hold/rewrite_test.py.
The rewritten test now fails, and its line names the wrong value. Put
SHIPPING = 4.95 back in your copy and run the tests once more: all
five pass again, and the rewritten test now proves the price.
Which rewrite would catch the wrong price?
Section titled “Which rewrite would catch the wrong price?”A pricing module adds 4.95 EUR shipping to orders below 50.00 EUR, from a constant SHIPPING. A test computes the expected total of an order by calling the same functions as the code, so it passes when SHIPPING holds a wrong price.
test_total_adds_shipping passed with the wrong price. Which line in
its place would fail while SHIPPING holds 5.95?
If SHIPPING held 5.95, which of these lines would still be true?
Keep the tests fast and unchanged
Section titled “Keep the tests fast and unchanged”An agent reruns the tests after every change. How long they take decides how many attempts fit in a loop: a one-second suite can run after each edit, and a twenty-minute suite gets run once at the end, or skipped. The example instructions file in the vendor guide for Claude Code has a line for this. It asks the agent to prefer running single tests to the whole suite, for speed [2]. Give the agent the fast tests for the part it changes, and run the full suite before the merge.
The agent also must not change the tests to get a pass. The shortest way to make a failing change pass is to delete the test, skip it or loosen its assertion, as the agent in the pitfall did. When an agent may edit the tests, the blast radius of one wrong change includes the check that should catch it. So the rule for an agent is that it never deletes or weakens a test. A test it thinks is wrong makes it stop and explain why, and the decision is yours.
A rule in the project instructions is a request. Claude Code reads its instructions file when a session starts and uses it as context, and nothing in the file is enforced [3]. The rule makes a weakened test less likely, and the review of the test files is what finds one.
The suite is too slow for the loop
Section titled “The suite is too slow for the loop”A team's full test suite takes 25 minutes. A coding agent is about to work on one module in a loop: change the code, run the tests, read the result, and try again, at most five times.
Which way of running the tests fits the agent’s loop?
Which option still gives the agent a result it can read after every change, without changing any test?
The tests pass, and one assertion changed
Section titled “The tests pass, and one assertion changed”A coding agent was asked to add discount codes to a pricing module and run its tests until they pass. The tests pass. In the diff of the test file, the line assert total(ORDER) == 49.95 now reads assert total(ORDER) >= 49.0.
The brief didn’t ask for a change to the total of an order without a discount code. What do you do?
If you put the old assertion back, what would it say about the code as it is now?
Documentation is part of the change
Section titled “Documentation is part of the change”Documentation is the written explanation of how a system behaves:
the README, the pages in docs/, and the comments and docstrings in
the code. An agent can write good documentation from code, and it can
just as easily change the code and leave the documentation saying
something else. Treat the docs as part of the change. The brief names
what to update, the project instructions say where the docs are, and the
review checks them.
The instructions file of the shop, AGENTS.md, says where its docs are.
The Claude Code guide suggests that an instructions file links to
detailed documentation and doesn’t copy it in [2].
- Run `python3 test_pricing.py` after every change, and read what it prints.- Never delete a test or loosen an assertion to make a change pass. If a test looks wrong, stop and say why.- Documentation is in three places: `README.md` for customers, `docs/pricing.md` for the full rules, and the docstrings in `pricing.py`. A change to a price or a threshold updates all three in the same change.The shop raises its free-shipping threshold. The brief and the reply below are illustrative and don’t record a specific model’s words.
Raise the free-shipping threshold in pricing.py from 50.00 EUR to
60.00 EUR, and add a test at the new threshold. Update the documentation
that AGENTS.md lists. Run python3 test_pricing.py after each change.
Done when the tests pass and none of the places AGENTS.md lists still
gives 50.00 EUR as the threshold.
I changed FREE_SHIPPING_FROM to 60.00 and updated the docstring of
pricing.py. I added test_free_shipping_starts_at_sixty, which checks
60.00 and 59.99. The tests print 6 passed, 0 failed.
The reply lists the docstring and says nothing about the other two places. You brief your own agent the same way in the exercise at the end of the lesson. To see what the change touched, make the copy a git repository with one tagged commit before the run:
git initgit add .git commit -m "Before the agent's change"git tag before-changeAfter the run, stage everything with git add -A, so that a file the
agent created shows as well, and list what differs from the tag. Then
search the three places for the old threshold.
What the change touched, and what it missed
Section titled “What the change touched, and what it missed”The course’s script makes the agent’s change to a fresh copy of the
shop, committed and tagged before-change first, and runs the same
commands you run after your own agent’s run in the exercise. You can
run it from site/examples/coding-with-agents/tests-and-docs-that-hold/
in your clone.
python3 the_change.py$ git add -A $ git diff --cached --stat before-change pricing.py | 4 ++-- test_pricing.py | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) $ python3 test_pricing.py test_subtotal_adds_the_lines: pass test_small_order_pays_shipping: pass test_large_order_ships_free: pass test_total_adds_shipping: pass test_total_of_a_known_order: pass test_free_shipping_starts_at_sixty: pass 6 passed, 0 failed exit status 0 $ grep -n "50.00" README.md docs/pricing.md pricing.py README.md:5:Shipping costs 4.95 EUR. Orders of 50.00 EUR or more ship free. docs/pricing.md:7:the free-shipping threshold, and free from 50.00 EUR. The threshold is
Output verified in CI from site/examples/coding-with-agents/tests-and-docs-that-hold/the_change.py.
The tests pass, and the list of changed files holds neither
README.md nor docs/pricing.md. The search finds the old threshold
in README.md and in docs/pricing.md, and nothing in pricing.py, whose docstring the
agent did update. A customer who reads the README expects free
shipping on a 55.00 EUR order and pays 4.95 EUR. Send the two stale
lines back to the agent, and review the next diff the same way.
The tests pass. Is the change done?
Section titled “The tests pass. Is the change done?”A coding agent raised a shop's free-shipping threshold from 50.00 EUR to 60.00 EUR. Its tests pass. The list of changed files holds pricing.py and test_pricing.py. The project instructions say the documentation is in README.md, docs/pricing.md and the docstrings in pricing.py.
What do you do before you approve the change?
Which of the places the instructions list does the list of changed files leave out?
A test survived the break
Section titled “A test survived the break”A learner reviews tests an agent wrote. They change one line of the code to make it wrong on purpose and run the tests. Some tests fail, and one test that is about that line still passes.
What does that tell you about the test?
What input did the test give the code, and what did it compare the result with?
Proves behavior, or mirrors the code?
Section titled “Proves behavior, or mirrors the code?”A reviewer sorts agent-written tests by whether they would fail if the code were wrong. A test that takes its expected value from the code it tests passes whatever that code does.
Would the test still pass if the code under test returned a wrong value?
The retry limit changed
Section titled “The retry limit changed”A coding agent changed the retry limit of a service from 3 to 5. Its tests pass. The project instructions say the documentation is in README.md and in docs/. The list of changed files holds only retry.py and its test.
What do you check before you approve the change?
Which files would say how many retries the service makes?
Which lines keep the tests a check you trust?
Section titled “Which lines keep the tests a check you trust?”A team writes the project instructions a coding agent reads before it works. They want the agent to run the tests in a loop, and they want the tests to stay a check the team trusts.
Which of these lines belong in the instructions?
Which lines give the agent a check it can run after each change, and which ones let it change the check?
One test is now skipped
Section titled “One test is now skipped”Before a coding agent's run, a project's tests printed 12 passed. After the run, the agent reports that the tests print 11 passed, 0 failed, 1 skipped, and that the work is done. The diff of the test file adds a skip marker to one test.
What does the report tell you?
What did the check cover before the run, and what does it cover now?
Exercise
The exercise takes about ten minutes and gives you one rewritten test and a short list of stale lines. It practices the checks that find a test that can’t fail and documentation that no longer matches the code.
From the root of your clone, make a fresh copy of the shop for the first part:
cp -R site/examples/coding-with-agents/tests-and-docs-that-hold/shop ~/shop-boundarycd ~/shop-boundaryIn shipping, change >= to >. Before you run the tests, write down
which of the five you expect to fail. Run python3 test_pricing.py and
compare. For every test that passes, name one bug in pricing.py it
would not catch. Then rewrite test_large_order_ships_free so that it
fails with the break in place and passes without it.
For the documentation, go back to the root of your clone, make one more fresh copy and give it a tagged commit:
cp -R site/examples/coding-with-agents/tests-and-docs-that-hold/shop ~/shop-docscd ~/shop-docsgit initgit add .git commit -m "Before the agent's change"git tag before-changeStart your own coding agent inside ~/shop-docs, so it works on the
copy and not on your clone, and give it the brief from this lesson.
When it is done, run these commands:
git add -Agit diff --cached --stat before-changepython3 test_pricing.pygrep -n "50.00" README.md docs/pricing.md pricing.pyA good result has a list of expected failures that matches the run, a
rewritten test that fails with the break and passes without it, and a
review that names every place AGENTS.md lists that still gives the old
threshold, or confirms that none does. Then answer one question: which
test in your own project would you break first, and why that one?
Stretch: Then pick one test an agent wrote in your own project, break the line of code it is about on purpose, and see whether the test fails.
Recap
- Review an agent’s test by making the code wrong on purpose and running it. A test that still passes either gives the code an input that never reaches the broken line, or takes its expected value from the code it tests, so it mirrors the implementation. Rewrite it with an input that reaches the line and the value the program must produce, from the spec or worked out by hand.
- Give the agent the fast tests for the part it changes to run in its loop [2], and run the full suite before the merge. The agent never deletes, skips or loosens a test to make a change pass. When a test looks wrong, it stops and says why.
- A rule in the instructions file is context for the agent, and nothing enforces it [3]. Read the diff of the test files to confirm that no test was deleted, skipped or loosened.
- Documentation is part of the change. The brief names what to update, the instructions say where the docs are, and the review searches those places for what the change made stale.
You can now
- Reviews code they did not write, against the specification
- Turns a check into a bounded, self-checking loop
References
Section titled “References”- Anthropic. The AI-native SDLC playbook. Claude Academy. Course.
Academy ai-native-sdlc-playbook - Anthropic. Best practices for Claude Code. Claude Code documentation. Reference.
Claude Code best practices - Anthropic. How Claude remembers your project. Claude Code documentation. Reference.
Claude Code memory