Programming with AI
ENRU
3.9

Template — write tests

writing them is optional, reading them is not

What it is

A test is code that checks other code. You will not have to write tests yourself — the AI writes them readily. But you will have to read them, and that pays off: a good set of tests is a list of what your program promises to do.

For anyone working with an AI, tests have a second use that matters more than the first: they stop you breaking what already worked. Ask for a change, run the tests, and you see at once whether the AI disturbed the neighbours.

What it looks like in code

First the list of cases, then the code

The key move: don't ask for tests straight away. Ask for a list of what to check.

Better
Here is the split_message function. List the cases worth
covering with tests. No code yet — just the list.
Mark which cases you consider important and which are
just-in-case.

The list reads in half a minute, and it shows immediately whether the two of you understood the task the same way:

AI reply
Important:
1. Text shorter than the limit — returned as one chunk
2. Text longer than the limit — split at paragraph boundaries
3. Joining the chunks equals the original text
4. A word is not broken in the middle

Just in case:
5. An empty string
6. Text exactly at the limit, character for character
7. A single paragraph longer than the limit

Case 7 is the one people usually forget. With the list in front of you, you decide what is needed and what is not, and only then ask for the code.

Prompt
Write pytest tests for cases 1–4 and 7. Skip the rest.

The template

Prompt
Code: <what we are testing>
With: <pytest / unittest / whatever the project already uses>
Cases: <the list agreed in advance>
Without: <mocks where avoidable; the real network; files on disk>

What to tell the AI

A test checks behaviour, not construction

A bad test breaks on any refactoring, even when the program still works correctly. That happens when the test peers inside: checking private fields, the order of calls, intermediate variables.

Prompt
The tests should check only what the function returns and its
visible effects. Don't peer into the internals: after a
refactoring the tests should still pass.

Ask for tests that fail first

A way to be sure a test checks anything at all:

Prompt
Before fixing the bug, write a test that reproduces it.
It must fail against the current code.

A failing test is proof the bug was understood correctly. If it is green straight away, it is checking the wrong thing.

Running them is not optional

Generated tests have to be run. It sounds obvious and gets skipped constantly — tests look convincing and create a false sense of coverage.

pytest -q

What to look for

A test that always passes. assert result is not None where the function cannot return None. Looks like a check, checks nothing.

Mocks instead of checking. The AI replaces half the system with stubs, and the test verifies that the stub returned what was put into it. Formally green, substantially empty.

Tests that repeat the implementation. If the test reproduces the function's algorithm line by line, it isn't checking correctness — it is checking that the code equals itself.

Only the happy cases covered. Empty data, None, a dropped connection, an input that is too long — that is where the bugs hide. If the set contains not one failing case, the set is incomplete.

Cheat sheet

  • First the list of cases, then the test code.

  • A test checks behaviour, not internal construction.

  • A test for a bug must fail before the fix.

  • A test that has never failed guarantees nothing.

  • Generated tests must be run.

  • Not one unhappy case in the set — the set is incomplete.