Back

Spec Driven Development + Test Driven Development in practice using AI

Spec Driven Development + Test Driven Development in practice using AI

Write a short spec, then ask an AI for one failing check and the smallest fix. A small calculator is only the practice case. You do not write the tests yourself.

1 Two habits, used together

Spec-driven development and test-driven development are often treated as rival methods. Each one answers a different question. The useful order is simple: decide what you are building, then prove one small piece at a time.

Spec-driven development means you write a short plan first. That plan is the spec. It lists what the program should do, what it should not do, and how you will know you are finished. You write this before you invent extra features.

Test-driven development means a check exists before the code that should satisfy it. A test is a tiny automatic check, like “12 plus 3 must come back as 15.” The first time it runs, it fails, because the feature is not there yet. Then only enough code is added to make that one check pass.

You do not write those checks yourself. You ask an AI, one spec line at a time. An AI here is a coding assistant you talk to in plain language. A prompt is the message you send it. Name the line, ask for a failing check only, wait until it fails, then ask for the smallest fix.

Spec-driven developmentTest-driven development
What you do firstWrite a short list of goals and limitsAsk the AI for one automatic check
Question it answersWhat are we building, and what are we skipping?Does this one piece still do what we said?
What goes wrong if you skip itYou add features nobody asked forThe code can be wrong and you notice late
  • The spec is the agreementIt names the work. If a feature is not in the spec, you do not build it yet
  • The prompt is the skillYou ask for one failing check, then the smallest code that makes it pass. You do not write the test
  • Together they stay honestThe spec names the behavior. The AI turns one line into a check you can run. The screen comes last
  1. SpecWrite what done looks like
  2. Ask failAsk for one check that is not satisfied yet
  3. Ask passAsk for only enough code for that check
  4. RepeatNext line from the spec

The order matters

If you are unsure what you are building, start with the spec. If a piece of code can quietly give the wrong answer, ask the AI for a failing check next. Keep each ask small. A prompt that says “write all the tests and the whole calculator” skips the method.

2 A small calculator as the practice case

I needed an example small enough to finish in one sitting. A calculator with add, subtract, multiply, divide, and a clear button is enough. Divide by zero should show the word Error. No memory keys. No extra science buttons. The calculator is only the practice case, so the prompts stay concrete. Finished page: thales.dev/calculator.

  • Small enough that a long planning process would be more than this job needs
  • One known special case, already named: dividing by zero

The sequence I used write the specask for a failing add checkask for the smallest addrepeat for the rest

3 Write the spec before any calculator code

I created a folder and asked the AI to install Vitest. Vitest is a program that runs the automatic checks. Then I stopped. I wrote a file called specs/spec.md: the problem, what is included, what is left out, and a few “when this happens, this should appear” lines. Later prompts point at this file.

  1. Create the project folder. Ask the AI to install Vitest. Stop there.
  2. Write the spec yourself: what we will build, what we will skip, and how we will know we are done.
  3. Tell the AI the math should live in its own file, away from the on-screen buttons. Do not ask it to write those functions yet.
Project folder with only package.json and Vitest, no calculator code yet
The folder at the start. Test tool installed. No calculator code yet.

The full spec file, specs/spec.md

# Calculator spec

## Rules (keep this small)

- Spec first. Do not invent features that are not listed here.
- Tests prove the engine. The UI only calls the engine.
- One failing test at a time. Write only enough code to pass that test.
- Out of scope is part of the contract. Ignore it on purpose.

## Problem

A visitor on thales.dev can open `/calculator` and do the four basic operations on two numbers, then clear the display.

## In scope

- Add, subtract, multiply, divide
- Clear
- Pattern: enter A, pick an operator, enter B, press `=`
- Divide by zero shows `Error`

## Out of scope

- Memory keys (M+, MR)
- Percent
- Parentheses or operator precedence chains
- Scientific functions
- Keyboard support

## Acceptance (EARS-style)

- When the user adds 12 and 3, the display shall show 15
- When the user subtracts 9 from 20, the display shall show 11
- When the user multiplies 6 by 7, the display shall show 42
- When the user divides 20 by 4, the display shall show 5
- When the user divides 10 by 0, the display shall show `Error`
- When the user presses Clear, the display shall reset to 0

## Engine contract

Pure functions in `calculator.js`. No DOM.

- `add(a, b)` returns a number
- `subtract(a, b)` returns a number
- `multiply(a, b)` returns a number
- `divide(a, b)` returns a number, or the string `Error` when `b` is 0

## Done when

- [ ] Spec written before production code
- [ ] Each operation has a failing test, then a passing test
- [ ] Divide-by-zero is covered
- [ ] UI uses the same engine
- [ ] Acceptance clicks match this file

That is the whole file. “EARS” is only a name for lines that say “when this happens, this should appear.” The “left out” list is part of the agreement. If it is not in the spec, I do not add it, even when it would be easy.

4 Ask for a failing check, then the smallest fix

The spec already said that 12 plus 3 should become 15. I did not type a test. I asked the AI for one automatic check of that line, and I told it to stop when the check failed. Add was missing, so the check received nothing useful. That failure is useful. It means the check is real, and the feature is not done yet.

  • Ask for one check only. Name the spec line. Do not ask for the calculator code in the same message
  • Stop when the check fails. A missing function name is a valid failure. Then send a second, smaller ask
  • The second ask is only “make this one check pass.” Do not add subtract, multiply, divide, or buttons

First ask: one failing check

Read specs/spec.md. Do not write the calculator yet.

Write one automatic check only: 12 plus 3 must come back as 15.
Use Vitest. Leave add missing or empty so this check fails.
Stop when the check has failed. Do not fix it yet.
Test report showing that the add check failed
After that ask, the check failed. It wanted 15 and received nothing useful.

Second ask: only enough code to pass

The add check failed. That is what we wanted.

Write only enough code to make that one check pass.
Do not add subtract, multiply, or divide.
Do not build the on-screen buttons.
Test report showing that the add check passed
After the second ask, the check passed. 12 plus 3 now comes back as 15.

Do not ask for the whole calculator in one go

The spec already listed four operations. I still asked only for add. If you tell the AI to write every check and every function at once, you skip the fail-then-pass habit.

5 Repeat the same asks for the next operations

Subtract, multiply, and divide (when the second number is not zero) used the same two prompts. Name the next spec line. Ask for one failing check. Wait. Then ask for only enough code to pass it. Dividing by zero is a later ask, because the spec already separated that case.

What I asked the AI to proveExample numbersExpected result
Subtract, one check only20 minus 911
Multiply, one check only6 times 742
Divide when the second number is not zero20 divided by 45

The same prompt, next spec line

Read specs/spec.md. Add is already proven.

Write one new automatic check only: 20 minus 9 must come back as 11.
Leave subtract missing so this check fails.
Stop when it has failed. Do not write subtract yet.

Each row is a pair of asks: fail first, then the smallest fix. If I had asked for a plain divide and stopped there, JavaScript would later return Infinity when someone divides by zero. Infinity means “this number has no end.” The spec asked for the word Error, so that case waited for the next prompt.

6 Divide by zero was already in the spec

I did not discover this problem by clicking buttons. The spec already said that 10 divided by 0 should show Error. I asked the AI to turn that sentence into one failing check. The current divide function returned Infinity. The check failed, which is what we want at this step.

Ask: one failing check for the Error line

The spec says 10 divided by 0 must show the word Error, not Infinity.

Write one automatic check for that line only.
Do not change divide until this check has failed.
Test report: the check expected Error and received Infinity
After that ask, the check failed. 10 divided by 0 was Infinity. The spec asked for Error.

Ask: only enough change to pass

The check failed because divide returned Infinity.

Change divide only enough so 10 divided by 0 returns the word Error.
Do not add extra features.
Test report showing all five math checks passing
After the second ask, all five math checks passed.

I did not type the fix. The AI added a simple rule: if the second number is 0, return the word Error. My part was the two prompts, then reading the report.

7 Ask for the on-screen buttons last

The automatic checks already proved add, subtract, multiply, divide, and the Error case. Only then did I ask the AI for the page you see. The buttons should not redo the math. They should call the four functions that already passed.

Ask: a screen that uses the proven functions

The math in calculator.js already has passing checks. Do not change those functions.

Build a simple page: a display that starts at 0, number keys, add, subtract, multiply, divide, equals, and clear.
When the person presses equals, call the matching function and show the result.
If divide returns the word Error, show Error on the display.

Try the finished page

That is the last prompt in this walkthrough. Open thales.dev/calculator to use it.

A warning: this is just a simple calculator. There is nothing special about the app. What matters here is the Spec-Driven Development and Test-Driven Development process.

8 References

These are the sources I used while writing this article. Some describe larger planning systems. Some describe the fail-then-pass habit. I ran that habit by asking an AI, not by typing the checks.

SourceWhat I used it for
Tekk, SDD vs TDDHow the two habits fit in one sequence
GitHub Spec KitA larger spec-driven toolkit for wider features
Spec-first, spec-anchored, spec-as-sourceNames for how tightly the spec sits on the work
Kent Beck, Canon TDDOne test, then make it pass
Uncle Bob, Cycles of TDDShort rules for how little code to write at each step
Martin Fowler, TDDA public explanation of fail, pass, then clean up
Hacker News on Spec KitWhen extra planning files help, and when they waste time
Back