Why I Write Tests Before AI Writes Code
AI coding works when the agent runs inside rules I set: fixed function contracts, known patterns, and tests that define done before the code exists.
I use AI tools every day, and I do not hand them a feature and hope. The method that works for me is to limit what the agent is allowed to decide.
The agent gets freedom inside a box. I build the box.
The problem
An agent given an open request makes a hundred small choices. It picks how to handle errors, where to put logic, and what to name things, and each choice is reasonable alone.
Across a large codebase those choices pile up into several styles of the same thing. That drift is the main risk I see with generated code, more than any single bad function.
What I fix in advance
A fixed return shape
Utilities that touch the database or other services follow one contract: they take a client and arguments, and return a result with data and error. The agent never has to decide whether to throw, return null, or return a custom object.
Composition rules
Endpoints validate the request and then call utilities. They do not carry business logic of their own, so the agent knows where new logic goes.
Existing examples
I point the agent at vetted code in the same repository. It copies a known pattern instead of inventing one, which is easier for me to review.
Tests define done
For every utility I write boundary tests first, covering null, undefined, invalid input, valid input, and a mixed or edge case. The test file is part of the specification.
The agent’s job is then narrow: write the function in the established style and make the tests pass. If its code fails a test, the failure tells me where it made a wrong assumption.
At one staging checkpoint on RE/AMP, the suite stood at 726 passing tests across 42 suites. I cite that as a snapshot of one body of work. It is not a coverage percentage.
The rule I add: no silent new patterns
If an agent needs something the codebase does not have, it must stop and say so. It does not create a new layer because it seems cleaner.
I then decide whether this is a real exception worth a new pattern, or whether the work should fit an existing one. Without that gate, a codebase slowly collects variants of the same idea.
What stays with me
- Architecture and data model. I design these before any generated code exists.
- Task breakdown. I split work into small pieces that can be tested and reviewed.
- Review. I read every change before it is committed.
The agent can implement. The decisions stay human-owned.
Keep tasks small
A smaller task means a smaller diff, and a smaller diff is easier to verify honestly. I break work into pieces that can be tested and reviewed on their own.
Takeaway
AI speeds up implementation, but it does not reduce the need for architecture, tests, or judgment. Strong constraints make the output more predictable, and predictable output is what makes review practical.
If you want this kind of discipline on your project, see the custom web applications service. For the larger picture, read The Hidden Costs of AI-Assisted Development.