Not sure what correct AI output looks like
Specifying what a correct answer looks like first prevents reliance on lucky outputs
Stop guessing whether a prompt got better — prove it
Writing your `input → expected` examples before you write the prompt, then editing the prompt until every example passes — the red → green → refactor loop borrowed from software's test-driven development.
A discipline, not a tool: borrow software's Test-Driven Development for prompting. Write a few input → expected examples first, then let the failing cases drive how you write and refine the prompt — the red → green → refactor loop. It turns "this answer looks good" into a checkable spec, so a prompt edit, a model swap, or a silent model update cannot break something quietly. → Unlike Prompt Engineering (which techniques to reach for), this chapter is the workflow that makes any of them dependable. → For how to actually grade the outputs — code checks, an LLM-as-judge, human review — see Evals & testing; TDPE is the loop, Evals is the machinery. Tool-agnostic; promptfoo is the running example.
The moment you catch yourself tuning a prompt by eye on one lucky output — or a prompt that worked last month quietly breaks after a model update.
A small example file (5-10 real input/output pairs, edge cases included), the red → green → refactor loop, and re-running the file on every prompt edit or model swap.
TDPE is the habit of writing examples first and re-running them — it does not teach you how to grade an answer. For code checks, LLM-as-judge, or a full regression suite, see the Evals & testing chapter.
A test case is an input → expected output pair written before the prompt exists, turning “does this work” into a checkable condition.
Prove a prompt works reliably by defining and testing expected outputs before writing it
Add a test case: Input – “Student asks about deadline”; Expected output – “The deadline is next Friday.” Then run the test suite.Enter this text in the Test Cases section of the test-driven-prompt-engineering interface and press Run Tests. Look for a red (failed) result before you start editing the prompt.
input → expected output pairs for the task you want to solveThe example file is a short table or plain‑text list of real inputs beside their correct outputs, grown by adding rows whenever the task fails in a new way.
Do this first Validate prompts with test cases
Produce a written, checkable spec of inputs and expected outputs before drafting any prompt
Create a plain‑text spec file named `spec.txt` containing five real input→expected pairs for the task “classify student emails as urgent or not”. Use actual email excerpts (you can copy them from your inbox) and write, in one sentence each, what the correct classification should be. Include at least one edge case you’ve seen mis‑classified before.Paste the prompt into the New Spec field on the test-driven-prompt-engineering dashboard and hit Generate. Verify that each line follows the input → expected format and that an edge case is present.
The red case is the first example your draft prompt fails, and it is the only thing the next edit is allowed to be about.
Do this first Create a concrete example specification
Find the first example your draft prompt fails and apply the smallest edit that makes it succeed
Run the current draft prompt on every example in the suite and output a pass/fail table.Paste this into the Prompt Runner input box of the test-driven-prompt-engineering tool and hit Run. Watch the results grid and note the first row that turns red – that’s the target example to fix.
Refactoring a prompt is removing a repeated instruction or an over‑long example while every test stays green — the shorter wording is a hypothesis, not an improvement, until the suite says so.
Do this first Drive prompt edits using a failing example
Refactor the prompt for clarity and lower cost while all examples stay green
Refactor the prompt in prompts.txt to make it clearer and shorter while ensuring every example still passes the test suite.Paste this command into the Prompt Editor screen of the test-driven-prompt-engineering tool, then click Run Tests. Watch for any examples turning red – that indicates a regression you must fix before proceeding.
The example file lives beside the prompt so it travels with it, and it is run again after every prompt edit, every model swap, and every silent change at the provider.
Do this first Tighten a passing prompt without breaking it
Make the example file a permanent companion to the prompt and execute it whenever you edit the prompt, switch models or the provider updates
test-driven-prompt-engineering run example_prompts.csvPaste this command into the Terminal of the test-driven-prompt-engineering UI after saving your example file next to the prompt. Watch the output table for any red ✗ entries indicating a regression before accepting the changes.
The red‑green‑refactor loop run end to end on a real task: write examples, edit until green, tighten, then execute the same unchanged file against a different model.
Do this first Re‑run examples after every change
Complete a real non‑code task using the red‑green‑refactor loop and verify it on a different model
In the test-driven-prompt-engineering UI, click **New Prompt**, paste:
Classify each student email into one of: question, deadline, complaint, other. If multiple categories apply, list them separated by commas.
Then press **Run** on the six example emails you prepared.Enter this in the Prompt Editor screen and hit Run. Observe the pass/fail grid – all rows should be green before you switch models.
38 outcomes in all — one per recipe below.
Not sure what correct AI output looks like
Specifying what a correct answer looks like first prevents reliance on lucky outputs
Want to see if a prompt meets its goal
Concrete example pairs let you objectively evaluate whether a prompt meets its goals
My prompt keeps failing
Applying the TDD red→green→refactor cycle to prompts yields reliable, maintainable wording
If you cannot defend a prompt to someone who only saw it fail once, the prompt is still tuned by intuition rather than evidence
Your prompt keeps missing tricky cases
Adding examples that previously broke the task forces the prompt to handle the hardest situations
I only have a few examples
Beginning with a few examples keeps the effort manageable, and you can continuously add new cases as they appear
Need a clear prompt specification you can verify
A written spec lets you verify prompt quality objectively rather than by eye
Unsure which prompt examples fail
You can see which examples your draft prompt fails on before making changes
You concentrate your fixing effort on the specific example that fails first
A failing prompt needs a tiny tweak
A tiny, defensible tweak fixes the failure without over-hauling the prompt
Ensures the fix didn't break any previously passing cases
Confirms the change is truly required, not a guess
You only start trimming a prompt after every test example is passing, ensuring you don't break functionality
Examples get lost in another folder
Storing the test examples alongside the prompt ensures you always remember to run them
Manually grading examples in a spreadsheet
You can begin testing with a simple spreadsheet and manual grading before automating
Manually rerunning examples for each prompt and model
Using promptfoo lets you rerun all examples with a single command, removing manual effort
Need clear examples to shape a prompt
Starting with a set of concrete examples lets you define correct behavior before writing any prompt
My prompt keeps failing tests
Running the prompt against examples and fixing failures (red) until all pass (green) produces a reliable prompt
Making the smallest possible change (adding a role, example, or delimiter) resolves failures without over-engineering
Prompt is too long after passing all tests
After achieving all-green, you can streamline the prompt and verify stability by re-running the test suite
Running the same test suite on a different model quickly reveals silent regressions that would affect real users
A single successful output is just luck; it doesn’t show the prompt will handle variations in inputs. Reliability means the prompt consistently gives right answers across many situations, not just once.
First write down the criteria for a “right” answer—specific wording, format, or content you expect. This definition guides you to craft a prompt that aims for those exact results instead of hoping for a lucky match.
They are concrete examples where you list an actual user input and the exact answer you want the AI to produce. Having these pairs lets you objectively test whether your prompt meets its goal.
Start with a failing example (red), make the smallest change that makes it pass (green), then once all examples pass, tidy up the wording without breaking anything (refactor). This disciplined loop keeps edits minimal and the prompt stable.
Add those problematic scenarios as extra input → expected output pairs in your specification. By testing against these hard cases you force the prompt to handle them correctly.
LLMs perform best when the number of tokens in a single request stays below roughly 100k, which Matt calls the "smart zone". Beyond that the attention graph grows quadratically and the model's output degrades into the "dumb zone".
Break a large feature into small, end‑to‑end vertical slices that cross all layers (schema, API, UI). Each slice can be completed and tested before moving to the next, keeping every request inside the smart zone.
The "grill me" skill prompts the model to ask you focused clarification questions, building a shared design concept before any code is written. This reduces mis‑alignment and keeps the conversation token‑efficient.
After a long interaction, you can either clear the context entirely or use a "compact" operation that summarizes prior exchanges into a short summary, freeing token space while preserving essential information.
Add a new feature without breaking existing code
TDD consists of five steps: write a failing test, verify it fails, write minimal code to pass, ensure all tests pass, then refactor. This forces you to clarify requirements before coding and keeps the codebase stable.
Tests interfere with each other's data
Using setUp creates a fresh fixture before each test method, preventing state leakage between tests. tearDown can clean resources after each test.
assertIsInstance checks the type of a result (e.g., float), while assertAlmostEqual handles floating‑point rounding errors by allowing a tolerance.
Reusing a single object across multiple test methods creates hidden dependencies; each test should create its own data or use setUp to get a fresh copy.
Comparing a function’s output to a hard‑coded expected value validates behavior, whereas comparing to another implementation merely mirrors code and can miss bugs.
Want consistent AI answers every time
A well‑crafted prompt combines context, task, persona, format, examples, and tone. Specifying each element guides the LLM to produce targeted, formatted output.
Prompt is vague and gets rambling replies
A weak prompt is vague (e.g., "Tell me about AI"), while a strong prompt adds audience, length, and format constraints, leading to concise, relevant answers.
Want a blog post but don’t know the structure
Dividing a large request into sequential sub‑tasks lets the model brainstorm, select, and then expand, giving you control over each stage.
Need content that sounds like a journalist
Assigning a persona (e.g., journalist, nutritionist) influences vocabulary and perspective, producing content that fits the intended voice.
My prompts are too long and costly
LLMs process text as tokens (≈4 characters each). Shorter, token‑efficient prompts reduce cost and improve model focus.
Each model has a maximum token window (e.g., GPT‑3.5 ≈8k, GPT‑4 ≈32k). Exceeding it causes earlier content to be dropped, leading to incomplete answers.
Prompt keeps missing key info
After receiving an output, analyze gaps and adjust the prompt (add constraints, clarify intent). Re‑prompting iteratively converges on the desired result.
Not sure stack methods work correctly
Jest is a JavaScript testing framework that looks for files ending in *.test.js. By writing a failing test first (red), implementing just enough code to pass it (green), and then refactoring, you get immediate feedback and confidence that your code meets the specification.
Want to make sure a web form works for users
Cypress runs in a real browser and provides a jQuery‑like API to interact with the DOM. By scripting user actions (visit, type, click) and asserting UI state, you verify that the whole application works from the user's perspective.
Need a quick vanilla JavaScript starter
`npm init vite@latest my-app -- --template vanilla` quickly creates a minimal Vite‑powered project with an HTML entry point, dev server, and build pipeline, giving you a clean environment to start writing code and tests.
Want one config to declare models, prompts and test suites
The promptfoo.yml file is the entry point where you declare which LLM providers to use, reference prompt files, and specify test suites. Keeping this configuration simple lets you scale testing across many prompts without duplication.
Running a prompt with many variable inputs
Each YAML test defines a description, variables that replace placeholders in the prompt, and an `assertions` block that validates the model output. By swapping variable values you can generate many test cases from one template.
Not sure if LLM response is a proper SQL NLQ
Promptfoo offers several assertion types (string contains, regex, Levenshtein distance, custom JS/Python, and LLM rubric) that let you programmatically verify correctness, style, or sentiment of model responses.
Running `promptfoo eval` produces a table with latency (ms), token counts, and model identifiers for each test case. By examining these metrics you can pick the cheapest fastest model that still meets your assertions.
Need a new feature without writing tests yourself
By prompting an AI coding agent with a test‑first instruction, the agent first writes failing unit tests for a new feature, then implements just enough production code to make those tests pass. This mirrors classic TDD and ensures each change is verified before integration.
Same buyout‑price code in Java and Python backends
Opening separate AI coding windows for each language lets you run identical TDD prompts side‑by‑side, letting the AI handle language‑specific syntax while preserving a unified feature design across backends.
Backend adds a buyout price and the page breaks
When a new backend capability (e.g., buyout price) is added, writing front‑end unit or integration tests first catches mismatches like missing UI handling for auction closure, avoiding broken user flows.
Need unit tests for a function
Copilot can create unit test files for a selected function using the "/tests" slash command in Copilot Chat. It inspects the highlighted code, infers expected behavior, and writes both passing and failing test cases, saving you from writing boilerplate test scaffolding.
Want to add a tiny feature using test‑driven development
TDD means writing a failing test first (Red), implementing just enough code to make it pass (Green), then cleaning up the implementation (Refactor). Copilot speeds each step: it can generate the initial test, suggest minimal production code, and help refactor while keeping tests green.
Need a username checker but don’t want to code
By specifying validation rules in natural language, Copilot can generate both unit tests and the corresponding implementation for a brand‑new function, demonstrating how to prototype features without writing any code manually first.
Want clear guidance for AI‑generated code
A spec is a concise, behavior‑focused description of what the system should do, including endpoints, inputs, outputs and error handling. By defining this contract first, you give the LLM clear guidance, reducing guesswork and iteration.
Spec lists inputs, outputs and edge cases
Because the spec enumerates inputs, outputs and edge cases, you can programmatically turn each clause into a unit test. This ensures the AI‑generated code is validated against the original contract.
Spec is just a contract
The spec acts as a contract; converting it into a design document breaks the work into concrete to‑dos (e.g., create model, write controller). This mirrors traditional SDLC phases while still using an LLM for code creation.
Have a spec and need code, unit tests, and docs
Instead of prompting for ad‑hoc code snippets, you give the entire spec as context. The LLM can then output implementation, unit tests, and documentation that all align with the same contract.
The same set on /recipes, filtered by tool and role.
This video delivers a comprehensive tutorial on prompt engineering for generative AI, teaching core techniques to structure, refine, and optimize prompts for reliable model outputs.
Demonstrates test-driven development in Python using unittest features
Explains how to turn specifications into design documents, use spec-driven prompting with LLMs to generate code, tests and documentation, and structure projects for AI-assisted development
Shows how to set up Promptfoo configurations, create variable-driven prompt tests, and use its assertion features to validate LLM outputs
A single successful output is just luck; it doesn’t show the prompt will handle variations in inputs. Reliability means the prompt consistently gives right answers across many situations, not just once.
First write down the criteria for a “right” answer—specific wording, format, or content you expect. This definition guides you to craft a prompt that aims for those exact results instead of hoping for a lucky match.
They are concrete examples where you list an actual user input and the exact answer you want the AI to produce. Having these pairs lets you objectively test whether your prompt meets its goal.
Start with a failing example (red), make the smallest change that makes it pass (green), then once all examples pass, tidy up the wording without breaking anything (refactor). This disciplined loop keeps edits minimal and the prompt stable.
Add those problematic scenarios as extra input → expected output pairs in your specification. By testing against these hard cases you force the prompt to handle them correctly.
red‑green‑refactor cycleRedGreenRefactorinput → expected outputedge casepass/fail gridexample filedefendability checkregression rundelimitermodel swap regression checkAsk, share, or report — over on the Heidelberg AI community forum.