## 1
1. The Testing Dilemma of Legacy Code
Almost every indie developer and small team runs into the same scenario: a project that's been running for three to five years, features piling up, but unit test coverage barely reaching 20%. Every time you modify a core module, you're on edge—"I touched this code, but did I break something else?"
Anyone who has manually written tests knows the pain:
- High time cost: For a moderately complex module, writing tests may take longer than writing the feature itself. Mocking dependencies, constructing boundary data, verifying assertions—half a day gone just like that.
- Awkward priority: Business deadlines always come first. Backfilling tests, something that "produces no new value," forever sits at the bottom of the to-do list.
- Old code nobody dares to touch: The original authors may have long since left. The logic is all guesswork, and it's unclear what behavior the tests should even verify.
- Refactoring on thin ice: Without a safety net of tests, refactoring is like walking a tightrope. Many teams simply settle on "code that doesn't change is good code."
That's the real state of most legacy projects. And the emergence of AI has conveniently pulled the cost of "backfilling tests" from "can't afford it" down to "just do it while you're at it."
2. What AI Can Do for Test Generation
Let's be clear about AI's capability boundaries in this scenario. It's not a fully automatic silver bullet, but it can handle 80% of the grunt work in testing:
1. Reading code and inferring test intent
Feed a function along with its context to AI, and it can analyze the core branches, boundary conditions, and exception paths. For example, given code that handles coupon redemption, AI can identify branches like "amount is zero," "coupon expired," and "insufficient inventory" that need separate coverage—traditionally the most brain-intensive part.
2. Generating test skeletons and mocks
Pattern-based generation is what AI does best: test class structure, dependency mocks, Given-When-Then organization, naming conventions—it can follow the existing test style in your project. You only need to review and fine-tune.
3. Constructing boundary test data
Null values, extreme values, special characters, extra-long strings, timing issues in concurrent scenarios—have AI "enumerate" these easily-missed boundaries, and it often finds cases you never thought of yourself.
4. Discovering code smells in reverse
While analyzing code to generate tests, AI frequently points out things like: "This function is 92 lines long; consider splitting it before testing," or "The null check here duplicates the caller's logic." Refactoring suggestions driven by testing are often more practical than plain code reviews.
3. Practical Workflow: Four Steps
I've summarized a directly reusable workflow, suitable for indie developers and small teams:
Step 1: Define the scope. Don't try to cover all your code at once. Run a report with a coverage tool (e.g., JaCoCo, coverage.py, Istanbul), pick the intersection of "frequently changed + low coverage + complex logic" modules, and start with the most painful 10%.
Step 2: Prepare the context. Give the AI the target code, the signatures of the interfaces it depends on, and existing test examples from your project. The existing test examples are critical—they determine whether the AI's generated style matches your project.
Step 3: Generate using a prompt template. Here's my well-polished template, ready to copy and use:
你是一位资深测试工程师。请为以下代码生成单元测试。
## 要求
1. 测试框架:{JUnit5 + Mockito / pytest / Jest 等,按项目填写}
2. 遵循 Given-When-Then 结构,测试方法命名用「场景_输入_期望结果」格式
3. 覆盖所有分支:正常路径、边界条件、异常路径
4. 对外部依赖使用mock,不要发起真实网络或数据库调用
5. 每个测试只验证一个行为,断言要具体,不要只断言不抛异常
6. 如果发现代码中疑似bug或无法测试的设计,在最后单独列出,不要写进测试
## 待测试代码
{粘贴目标代码}
## 依赖接口签名
{粘贴依赖的interface或函数签名}
## 项目已有测试示例(供参考风格)
{粘贴一两个现有测试}Step 4: Manual review + run verification. AI-generated tests must be executed. There are two possible outcomes for failures: the AI's assertions are wrong (delete or fix them), or—congratulations—the AI's assertions exposed a real bug in the legacy code. In my practice, I've encountered the latter more often than expected.
4. Before and After: The Real Change in Efficiency
Take a recent personal project I worked on (Python backend, ~8,000 lines of core logic, original coverage 31%):
| Dimension | Purely Manual Testing | AI-Assisted |
|---|---|---|
| Time to add tests for one function | 30-60 minutes | 2 minutes to generate + 10-15 minutes to review and fix |
| Test backfill cycle for 300 functions | Estimated ~2 months of spare time | 3 weeks, ~1 hour per day |
| Final coverage | Hard to sustain | Raised to 78% |
| Legacy bugs discovered along the way | 0 | 4 (2 null pointer boundary issues, 1 date calculation error, 1 cache not invalidated) |
This is data from my personal project, for reference only—your results will vary depending on project complexity. But the trend is certain: costs dropped by roughly 60-70%, and the psychological barrier to "getting started" dropped to nearly zero.
The more important hidden benefit: after coverage improved, I finally did a long-postponed refactoring. The test safety net gave me the confidence to touch a payment callback module I hadn't dared to modify in three years. This confidence to "change code" is the real value behind the coverage numbers.
5. A Few Pitfalls to Avoid
- Don't blindly accept generated results. AI "guesses" behavior, and for ambiguous logic it may generate wrong assertions that merely conform to the current implementation. When reviewing, first ask yourself: does this assertion describe the "expected behavior" or just "what currently happens to be true"?
- Don't chase 100% coverage. Tests for getters/setters and pure configuration code add no value. Save the AI's effort for complex logic.
- Put the generated tests into CI. Otherwise, the backfilled tests will quickly rot away again.
- Mind data security for sensitive code. When business logic or user data is involved, prioritize AI services that support private deployment or don't use your data for training.
6. Tool Selection and Getting-Started Advice
Today's AI coding tools (IDE plugins, chat-based, API-based) can all handle this task; the differences are mainly in context length, depth of code understanding, and cost. For a "batch-oriented, structured" task like test backfilling, accessing a large model via API for batch processing is often more efficient than manually pasting code one by one—especially suitable for small teams doing concentrated cleanup of legacy code.
If you haven't started yet, I suggest taking the most headache-inducing module in your project today and trying the prompt template above. The first time you see AI generate in two minutes the 20 test cases you would have spent an entire afternoon writing, you'll understand why this is called one of the highest ROI scenarios in AI-assisted programming.
If you want to access large models via API at low cost for batch test generation, check out this platform: https://api.thistoken.ai/register — register and use it right away, with pay-as-you-go pricing suited to the habits of indie developers and small teams.
Rather than endlessly worrying about your legacy code's testing debt, let AI help you start paying it down today.
---
Want to run the example right away? Visit https://api.thistoken.ai/register to sign up for ThisToken.AI, get your API Key, and start immediately.
Vous voulez essayer Token.AI ?
Créez une API Key au niveau du projet, activez les canaux dans la console et configurez le routage, les budgets et les journaux d'audit.
注册 ThisToken.AI 并获取 API Key