Back

Spec Driven Development + Test Driven Development in practice using AI

Spec Driven Development + Test Driven Development in practice using AI

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

1 Two habits, used together

Spec-driven development and test-driven development answer different questions. They belong in one order: decide what you are building, then prove one small piece at a time.

Spec-driven development means a short plan exists 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 do not type that file. You prompt an AI for it, then you read what came back 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 the spec or those checks yourself. You prompt an AI, then you check the result. An AI here is a coding assistant you talk to in plain language. A prompt is the message you send it. First prompt for the spec and read it. Then name one line, prompt for a failing check only, wait until it fails, then prompt for the smallest fix.

Spec-driven developmentTest-driven development
What you do firstPrompt the AI for a short list of goals and limits, then read itPrompt 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 wantedThe code can be wrong and you notice late
  • The spec is the agreementIt names the work. You prompt for it, then you check it. If a feature is not in the spec, you do not build it yet
  • The prompt is the skillYou prompt for the spec, then one failing check, then the smallest code that makes it pass. You do not write the spec or 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. SpecPrompt for what done looks like, then check it
  2. Fail promptPrompt for one check that is not satisfied yet
  3. Pass promptPrompt for only enough code for that check
  4. RepeatNext line from the spec

The order matters

If you are unsure what you are building, prompt for the spec first and read it. If a piece of code can quietly give the wrong answer, prompt the AI for a failing check next. Keep each prompt 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 quickly. 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. The finished calculator is at the end of this article.

  • 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 prompt for the specprompt for a failing add checkprompt for the smallest addrepeat for the rest

3 Prompt for the spec before any calculator code

I did not type the spec. I prompted the AI to install Vitest and write specs/spec.md in the same message. Vitest is a program that runs the automatic checks. Then I stopped and read the file. Later prompts point at it.

  1. Prompt the AI to install Vitest and write the spec. Stop there.
  2. Read what came back: what we will build, what we will skip, and how we will know we are done. Prompt again only if the file is wrong.
  3. Do not prompt for the math functions yet. The spec already says they should live in their own file, away from the on-screen buttons.
PROMPT
Create a small Node project in this folder. Install Vitest so we can run automatic checks later. Then write specs/spec.md for a four-function calculator on thales.dev at /calculator. Include the problem, what is in scope, what is left out, and EARS lines (when this happens, this should appear). In scope: add, subtract, multiply, divide, clear. Divide by zero shows the word Error. Out of scope: memory keys, percent, parentheses, scientific functions, keyboard. Math should live in calculator.js as plain functions, away from on-screen buttons. Do not write the calculator functions yet. Do not build the page yet. Stop when Vitest is installed and specs/spec.md exists. I will read the spec before we continue.
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 can use a small calculator with 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
# 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 can use a small calculator with 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 file the AI wrote. I read it before any calculator code. “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 Prompt 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 prompted 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.

  • Prompt for one check only. Name the spec line. Do not prompt 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 prompt
  • The second prompt is only “make this one check pass.” Do not add subtract, multiply, divide, or buttons
PROMPT
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 prompt, the check failed. It wanted 15 and received nothing useful.
PROMPT
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 prompt, the check passed. 12 plus 3 now comes back as 15.

Do not prompt for the whole calculator in one go

The spec already listed four operations. I still prompted 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 prompts for the next operations

Subtract, multiply, and divide (when the second number is not zero) used the same two prompts as add. Name the next spec line. Prompt for one failing check. Wait. Then prompt for only enough code to pass it. Dividing by zero is a later prompt, because the spec already separated that case. I am showing the subtract fail prompt below. The pass prompt was the same shape as add: only enough code for that one check.

What I prompted 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
PROMPT
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 prompts: fail first, then the smallest fix. If I had prompted 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 called 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 prompted 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.

PROMPT
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 prompt, the check failed. 10 divided by 0 was Infinity. The spec called for Error.
PROMPT
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 prompt, 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 Prompt for the on-screen buttons last

The automatic checks already proved add, subtract, multiply, divide, and the Error case. Only then did I prompt the AI for the calculator at the end of this article. The buttons should not redo the math. They should call the four functions that already passed.

PROMPT
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.

This is the result

That is the last prompt in this walkthrough. The calculator below is what those prompts produced.

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 prompting an AI, not by typing the spec or 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