You asked the AI to write a function. It gave you something that looks right. It even runs. But is it actually good?
Most engineers stop there. They see green and move on. That habit will quietly cause you problems.
AI coding tools like GitHub Copilot, Cursor, and Claude are genuinely useful. But they're non-deterministic, meaning the same prompt can produce different outputs on different days.
They can produce code that's plausible-looking but subtly wrong, or code that works for the happy path but falls apart on edge cases. Without a system for evaluating what the AI gives you, you're essentially shipping untested third-party code and hoping for the best.
This guide walks you through a practical, beginner-friendly approach to evaluating AI-generated code, so you can use these tools with confidence instead of crossed fingers.
What We'll Cover:
Why AI Code Needs Its Own Evaluation Discipline
When a human colleague writes code, you can ask them questions. You can read their commit history. You have context.
When an AI writes code, you have none of that. The output arrives fully formed, often with confident-sounding comments, and it's easy to assume competence where there may be none.
The other problem is that AI models are trained on vast amounts of public code, including bad public code. They can reproduce anti-patterns fluently. They can write code that passes a quick read but fails under real-world load, unusual inputs, or security scrutiny.
Evaluating AI code isn't about distrusting AI. It's about applying the same engineering discipline you would to any code that enters your codebase.
Step One: Define Correctness Before You Generate
The single most effective thing you can do is write your tests before you ask the AI to write the implementation. This is the spirit of test-driven development (TDD), and it maps perfectly onto AI-assisted workflows.
When you define correctness upfront, you give yourself an objective measure the moment the code arrives. You're not eyeballing it. You're running it against a contract you wrote yourself.
Here's a simple example. Say you want an AI to write a function that parses a price string like "$12.99" and returns a float. Before prompting the AI, write this:
def test_parse_price():
assert parse_price("$12.99") == 12.99
assert parse_price("$0.00") == 0.0
assert parse_price("$1,299.99") == 1299.99
assert parse_price("") is None
assert parse_price("free") is None
Now prompt the AI: "Write a Python function called parse_price that takes a price string like $12.99 or $1,299.99 and returns a float. Return None for invalid input."
Run your tests immediately. The AI might pass four out of five. Now you know exactly what to fix and you didn't have to read a single line of implementation to find the gap.
Step Two: Build a Golden Dataset
A golden dataset is a small collection of inputs with known correct outputs. Think of it as a permanent test suite for any AI feature you build. You start with five or ten examples. You add to it whenever something breaks in production.
This becomes your regression set. Every time you tweak a prompt, upgrade a model, or refactor a pipeline, you run the golden dataset first. If anything breaks, you know immediately.
Here's what a golden dataset might look like for the price parser. A simple JSON file works fine:
[
{ "input": "$12.99", "expected": 12.99, "note": "basic case" },
{ "input": "$1,299.99", "expected": 1299.99, "note": "thousands separator" },
{ "input": "12.99", "expected": 12.99, "note": "missing dollar sign" },
{ "input": "$ 12.99", "expected": 12.99, "note": "space after symbol, from prod bug #142" },
{ "input": "€12.99", "expected": null, "note": "unsupported currency" },
{ "input": "free", "expected": null, "note": "non-numeric text" },
{ "input": "", "expected": null, "note": "empty string" }
]
Each entry is just an input, the correct output, and a short note on why it's there. A script loads the file, runs each input through your function or prompt, and compares results.
For a code-generation use case, the same idea scales up: a folder of input prompts paired with expected output files, diffed by a script. For data extraction, a CSV of sample inputs alongside expected parsed values.
So how do you decide what goes in? Three sources cover most of it.
First, the representative cases: the ordinary inputs your feature handles ninety percent of the time. Second, the boundary cases you can predict upfront, like empty strings, unusual formats, and inputs that should be rejected. Third, and most valuable, real failures.
Notice the $ 12.99 entry above tagged with a production bug number. A user hit that input, the parser choked, and now it's in the dataset forever. That's the test: if an input broke something once, or plausibly could, it earns a permanent spot. If it's just a minor variation of a case you already cover, skip it and keep the dataset small enough to run on every change.
The key discipline is this: don't just fix the failing case. Add it to the golden dataset, fix it, and verify everything else still passes. This is how you stop the whack-a-mole problem where fixing one AI failure silently breaks three others.
Step Three: Measure Reliability, Not Just Correctness
AI outputs aren't deterministic. Correct once doesn't mean correct always. This is especially important if you're embedding AI into a product: a prompt that works 80% of the time will fail your users 20% of the time, and that's not acceptable in production.
The fix is to run your evaluation across multiple samples. Run the same prompt ten times and check how many outputs pass your tests. Tools like promptfoo make this easy to automate. You define your test cases in a config file, point it at your prompt, and it runs the evals and reports pass rates.
Here's what a simple promptfoo config looks like:
prompts:
- "Parse the following price string and return only a float: {{input}}"
providers:
- openai:gpt-4o
tests:
- vars:
input: "$12.99"
assert:
- type: equals
value: "12.99"
- vars:
input: "$1,299.99"
assert:
- type: equals
value: "1299.99"
- vars:
input: "free"
assert:
- type: equals
value: "null"
Run this across ten iterations and you'll quickly see if your prompt is brittle. A 100% pass rate across ten runs gives you real confidence. A 70% rate tells you the prompt needs tightening before it goes anywhere near production.
Step Four: Review for What Tests Can't Catch
Tests tell you if code is correct. They don't tell you if it's readable, maintainable, or secure. After your automated checks pass, do a focused human review on three things.
The first is security. AI models can produce code with real vulnerabilities like SQL injection via string concatenation, missing input sanitization, and hardcoded credentials in examples it then forgets to flag. Run AI-generated code through a static analysis tool like Bandit for Python or ESLint with a security plugin for JavaScript as a baseline check.
The second is edge cases the AI didn't consider. Look at the test cases you wrote and ask: what did I not cover? Empty lists, null values, very large inputs, concurrent calls, and so on might not be handled by AI. You need to push it on the edges.
The third is over-engineering. AI sometimes produces elaborate solutions to simple problems. If you asked for a function that checks whether a number is even and got back a class with three methods and a configuration object, that's a red flag.
Complexity is a cost. Prefer simple code you understand over clever code you do not.
Step Five: Treat Prompt Changes Like Code Changes
If you're using AI in a repeatable way like an internal tool, a product feature, or a script you run regularly, your prompts are part of your codebase. Version control them. Review changes to them. Don't just edit a prompt and hope for the best.
The practical habit is to store prompts in files rather than hardcoding them inline, commit them to Git alongside your code, and re-run your golden dataset any time a prompt changes. This takes maybe ten minutes to set up and saves hours of debugging later.
LangSmith and Weights & Biases both offer prompt versioning and eval tracking if you want a more structured solution. For most small projects, a prompts folder in your repo and a simple test runner is enough.
The Mindset That Makes This Work
Every technique in this guide comes down to one shift: treat AI outputs like external inputs, not trusted code.
You wouldn't deploy an API response to production without validating its shape. You wouldn't accept a file upload without checking its contents. AI-generated code deserves the same skepticism, not because the AI is unreliable, but because all external inputs are unreliable, and good engineering accounts for that.
The engineers who get the most out of AI tools aren't the ones who trust them most. They are the ones who verify fastest. Write the tests first. Build the golden dataset. Measure reliability. Review what automation misses. Version your prompts.
Do those five things consistently and you'll ship AI-assisted code with the same confidence you bring to anything else in your stack.
Hope you enjoyed this article. You can connect with me on LinkedIn.