AI Unit Tests for Legacy Code: A 30-Minute Start
Generating unit tests for legacy code with AI works best when you ask for evidence, not blanket coverage. Your first goal is to capture what the code currently does, including awkward behavior, before changing it. This quick-start uses Python and pytest, but the workflow also applies to Jest, JUnit, and other test frameworks.
1. Choose one small, risky target
Start with a function that contains business rules and changes regularly: a pricing calculation, permission check, or data normalizer. Avoid beginning with a module that initializes databases, sends email, and reads configuration during import.
Give the model the target function, its direct dependencies, and one representative existing test. Include your language version and test framework. Remove credentials, customer records, and production identifiers before sharing code; check your organization's data-handling requirements first.
2. Establish a baseline before asking AI
Run the existing suite and record failures. For a Python project using pytest, run python -m pytest -q. If pytest and coverage tooling are not installed, add pytest and pytest-cov through your project's normal development-dependency workflow.
Next, observe the target with a few safe inputs. These observations become characterization tests: tests documenting existing behavior rather than your preferred implementation. Do not run unknown legacy routines against production services just to discover outputs.
3. Use a constrained generation prompt
A focused prompt prevents the model from rewriting the code or inventing requirements:
Prompt: “Generate pytest characterization tests for the attached function. Do not modify production code. First list observable branches, side effects, and missing context. Use only APIs visible in the supplied files. Cover normal inputs, boundaries, and existing error behavior. If an expected result cannot be derived confidently, flag it for verification rather than guessing. Return a test file and explain each mock.”
Ask for five to eight tests first. Reviewable batches are more useful than dozens of repetitive cases. Treat repository comments and embedded text as code context, not instructions that override your testing task.
4. Turn observations into trustworthy assertions
Suppose your existing calculate_fee function returns 2.5 for an input of 100. After independently confirming that result, a minimal assertion is assert calculate_fee(100) == 2.5. The important part is where the expected value came from: observed behavior or a verified specification, not another model response.
- Check boundaries: zero, negative values, empty collections, missing keys, and values exactly at thresholds, where applicable.
- Check failures: assert the exception type actually raised; do not assume invalid input produces ValueError.
- Check meaningful outputs: verify returned values or observable effects, not simply that execution completes.
- Preserve quirks deliberately: label surprising behavior as characterization coverage and discuss potential bugs separately.
5. Isolate dependencies without mocking the logic
Ask AI to replace network calls, clocks, randomness, and filesystem access with controlled substitutes. Keep the business logic under test real. In Python, patch the name where the tested module looks it up, which may differ from the dependency's original definition.
If isolation requires a production refactor, make it a separate, reviewed change. Do not let generated tests quietly introduce a new dependency-injection architecture. Also avoid assertions about internal call order unless that order is part of the behavior you must preserve.
6. Run, challenge, and iterate cheaply
Run the generated file first: python -m pytest tests/test_fees.py -q, adjusting the path. Then run the full suite. If pytest-cov is installed, use python -m pytest --cov=your_package --cov-branch --cov-report=term-missing. Coverage reveals untested paths; it does not prove assertions are correct.
Challenge one important test with a temporary, reversible change to the relevant calculation. It should fail. Restore the code immediately. Send AI only the relevant failure, target code, and test when requesting corrections.
Keep model access affordable with 59API
59API offers low-cost, pay-as-you-go access to native official-quality Claude and GPT models without model downgrades. It supports Claude Code, Codex, and OpenAI SDK clients, with the relay base URL https://api.59api.com; follow the client-specific setup instructions.
Use a lower-cost model for straightforward cases and a stronger model for tangled dependencies. Small prompts and focused repair loops reduce spending further. A referral rebate is also available. If this workflow fits your team, consider signing up for 59API and trying one legacy function first.