AI Unit Tests for Legacy Code: Fix the Hard Parts
Start with behavior, not a rewrite
Generating unit tests for legacy code with AI works best when the model has evidence, not just a function signature. Older systems often hide business rules in conditionals, global state, and database calls. A plausible-looking test can still encode the wrong requirement.
Begin with characterization tests: tests that document what the code currently does. Then distinguish behavior worth preserving from known defects. Do not silently turn a suspected bug into a permanent specification.
What should you give the AI?
Choose one function or small class. Provide its implementation, directly relevant helpers, dependency versions, and an existing test showing project conventions. Include sanitized examples of inputs and observed outputs. Explain whether production changes are allowed.
Use a constrained prompt: “Generate pytest characterization tests for this function. Do not change production code. Cover normal inputs, boundaries, and observed exceptions. Mock only external I/O. Identify uncertain expectations instead of inventing business rules. Return the test file and explain each fixture.”
Remove credentials, customer records, and proprietary data you are not authorized to share. Check both the model provider’s and relay’s data-handling policies before sending private repositories.
How do you establish a reliable baseline?
- Run existing tests first. Record pre-existing failures so new tests are not blamed for unrelated problems.
- Install the test tools. In your project’s virtual environment, run python -m pip install pytest pytest-cov, following your repository’s dependency policy.
- Capture representative behavior. Exercise normal values, empty inputs, boundary values, and failure cases in a safe local environment.
- Generate a small batch. Ask for five to ten focused cases, not hundreds of speculative assertions.
- Run and inspect. Use python -m pytest tests/unit -q --maxfail=1, adjusting the directory to match your repository.
If an observed parser accepts “yes” but rejects “ YES ”, record that distinction explicitly. Ask the maintainer whether whitespace sensitivity is intentional before making it a long-term requirement.
Why do generated tests fail immediately?
Wrong imports or invented interfaces: supply the actual module path and constructor signature. Ask the model to use only symbols present in the supplied code. Do not add production methods merely to satisfy generated tests.
Incorrect patch targets: Python mocks must usually replace the name where the tested module looks it up. If billing.py imports fetch_rate directly, patch billing.fetch_rate rather than the original library’s fetch_rate.
Import-time side effects: a module may open connections before a test starts. Prefer an existing initialization seam. If none exists, consider a separately reviewed refactor that defers connection creation; do not disguise that production change as test generation.
How do you fix brittle or nondeterministic tests?
Control time, randomness, environment variables, and filesystem access. With pytest, use monkeypatch for environment changes and tmp_path for temporary files. Patch the clock where it is referenced, or inject one if a small production change is approved.
Mock network and database boundaries, not every internal helper. Excessive mocking can prove only that mocks call other mocks. Test observable results and meaningful side effects rather than private call order. Keep database-backed checks in a separate integration suite.
How can you tell whether the tests are useful?
Run python -m pytest tests/unit --cov=your_package --cov-branch --cov-report=term-missing, replacing your_package with the real importable package. Inspect uncovered branches, not just the headline percentage.
Ask the AI to explain which regression each assertion catches. In a disposable branch, deliberately invert a condition or remove a boundary check: a relevant test should fail. Review the diff to ensure expectations were not weakened simply to make the suite green.
Can you keep AI testing costs low?
Send the target module, relevant dependencies, and concise failure output rather than repeatedly uploading the repository. Use lower-cost models for straightforward cases and stronger models for tangled dependencies.
59API offers low-cost, pay-as-you-go access to native, official-quality Claude and GPT models without downgrading them. Its compatibility with Claude Code, Codex, and OpenAI SDKs fits existing workflows; its API base URL is https://api.59api.com. A referral rebate provides another potential saving. Consider signing up to trial a small testing task, while keeping execution and review local.
Pronto para começar?
Conecte Claude e GPT em minutos pelos menores preços, sem cortes. Cadastre-se e obtenha sua chave API.
Cadastro grátis