AI Unit Tests for Legacy Code: A Practical Guide
AI Unit Tests for Legacy Code: A Practical Guide
Legacy code is often risky to change because nobody fully knows what it does. Unit tests create a safety net, but writing them manually for old modules can be slow and tedious. AI can help you generate useful test coverage faster, especially when you combine it with a disciplined workflow. This tutorial shows a practical way to use AI to create unit tests for legacy code without blindly trusting generated output.
Step 1: Choose the Right Legacy Target
Do not start with the most complex file in the system. Pick a module that is important, relatively isolated, and currently painful to modify. Good candidates include pricing logic, validation functions, parsers, date handling utilities, permission checks, and data transformation services.
Before using AI, gather three things: the source file, any related interfaces or types, and a short explanation of how the module is used in production. If the code depends heavily on databases, queues, or external APIs, identify where mocks or fakes will be needed.
Step 2: Create a Characterization Test Goal
For legacy systems, the first objective is usually not to prove the code is ideal. The objective is to lock down current behavior. These are called characterization tests. They describe what the code does today, including odd edge cases, so you can refactor later with confidence.
Ask the AI to analyze the code and list observable behaviors before writing tests. A strong prompt is: Read this legacy module and identify the public behaviors that can be tested without changing production code. Include normal cases, boundary cases, error cases, and suspicious behavior that should be preserved until reviewed.
This step prevents the model from inventing a cleaner design that does not match your real system.
Step 3: Use a Low-Cost AI API for Iteration
Generating tests is iterative. You will ask the model to inspect code, draft tests, fix failing tests, improve mocks, and add edge cases. That can become expensive if you use premium models directly for every request. This is where 59API is useful for development teams.
59API is an AI API relay with cheap, pay-as-you-go access to Claude models such as Opus, Sonnet, Haiku, and Fable, plus GPT models. It uses native official-quality models without downgrade and is compatible with Claude Code, Codex, and any OpenAI SDK. The API base URL is https://api.59api.com, so you can usually point your existing tooling at it with minimal configuration. For test generation, this low-cost setup is ideal because you can afford many small review-and-rewrite cycles instead of trying to get a perfect answer in one prompt.
Step 4: Ask AI to Generate the First Test File
Once the behaviors are listed, request a first test draft using your actual test framework. Be specific. Mention the language, framework, file path, naming conventions, and mocking library. For example, tell it whether you use Jest, Vitest, pytest, JUnit, NUnit, Go testing, or RSpec.
A useful prompt structure is:
- Context: Explain the module purpose and any known constraints.
- Source: Paste the target file and relevant types.
- Framework: Specify the exact test runner and assertion style.
- Goal: Generate characterization tests for current behavior only.
- Rules: Do not modify production code, do not call real external services, and use deterministic inputs.
Ask the model to include test names that describe behavior clearly. Good generated tests are not just executable; they are documentation for future maintainers.
Step 5: Run the Tests and Feed Back Failures
Never assume generated tests are correct. Run them locally. Some will fail because the AI misunderstood a dependency, imported a module incorrectly, or guessed at behavior. Copy the exact error output back into the AI along with the test file and ask for a minimal fix.
Keep the feedback narrow. Instead of asking it to rewrite everything, say: These three tests fail with the following errors. Fix only the test code. Do not change the production module. Preserve the purpose of each test.
This loop is where AI becomes most valuable. It can quickly adjust mocks, handle async behavior, correct setup code, and align with your repository’s conventions.
Step 6: Improve Coverage Intelligently
After the first passing test file, run your coverage tool. Look at uncovered branches, not just line coverage. Ask the AI to suggest additional tests for the uncovered conditions, but review whether those branches matter. Legacy code often contains dead paths or defensive checks that are difficult to trigger.
Useful additions often include null or empty inputs, invalid enum values, timezone boundaries, rounding behavior, duplicate records, permission failures, retries, and exception handling. Ask the AI to generate one small batch at a time. Large batches are harder to review and more likely to contain brittle assumptions.
Step 7: Refactor Only After the Safety Net Exists
Once characterization tests pass, you can begin refactoring. At this point, AI can help in a different mode: improving design while keeping behavior unchanged. Ask for small refactors such as extracting pure functions, replacing duplicated conditionals, or separating I/O from business logic.
Run the tests after every change. If a refactor breaks a characterization test, decide whether the test captured intentional behavior or an existing bug. Do not automatically update tests just because the new implementation seems cleaner.
Step 8: Add AI Test Generation to Your Workflow
For long-term value, make this repeatable. Add a checklist to pull requests: identify risky legacy files, generate characterization tests, run coverage, review AI assumptions, and commit only tests that pass locally. You can also configure Claude Code, Codex, or OpenAI SDK-based internal tools to route through 59API at https://api.59api.com for affordable daily use. The referral rebate can also help teams reduce costs as adoption grows.
If you want to experiment without committing to a large AI bill, consider signing up for 59API and using it as your low-cost relay while you iterate on test generation prompts.
Final Checklist
- Start with one important but manageable legacy module.
- Generate behavior lists before generating tests.
- Use characterization tests to preserve current behavior.
- Run every AI-generated test locally.
- Feed exact failures back to the model for focused fixes.
- Increase branch coverage in small, reviewable batches.
- Refactor only after tests provide a safety net.
AI will not magically understand your entire legacy system, but it can dramatically reduce the effort required to build the first layer of protection. With careful prompts, local verification, and an affordable relay like 59API, you can turn untested legacy code into code your team can safely improve.
¿Listo para empezar?
Conecta Claude y GPT en minutos a los precios más bajos, sin recortes. Regístrate para obtener tu clave API.
Registro gratis