Image: AI generated
テストのないレガシーコードをAIでリファクタリングしたいなら、LLMにテストを書かせたのに途中で止まるなら、カバレッジを機械的に追跡しながらエージェントを制御したいなら――tsmaがその防御線を構築する。
テストのないコードをどうリファクタリングするか?
10万行のレガシーコードを引き継いだ。テストがない。リファクタリングしたいが、触れば何が壊れるかわからない。テストを書くにはコードを理解する必要があり、コードを理解するにはドキュメントが必要だが、ドキュメントもない。
誰も触らない。さらに腐っていく。
世界中のすべてのレガシーコードがこのデッドロックに陥っている。Fortune 500企業のIT予算の60~80%がレガシーの維持に費やされている。開発者の時間の42%が技術的負債の処理に消えている。
LLMがテストを代わりに書いてくれるとしたら?
LLMにテストを任せると生じる問題
LLMに「この関数のテストを書いて」と言えば、何かは出てくる。問題は3つある。
第一に、どこから始めるべきかわからない。 関数が527個あるとき、1番から順番に? 最も重要なものから? 基準がない。
第二に、テストの質を検証できない。 LLMが書いたテストがpassした。しかしそのテストは本当に関数の動作を検証しているのか、それとも呼び出すだけでassertのない空っぽなのか? 人が一つずつ読まなければわからない。
第三に、フィードバックがなければLLMのテストは60~70%で止まる。 Schafer等の実証研究(2023)によれば、LLM生成テストの中央値はstatement coverage 70.2%、branch coverage 52.8%にとどまる。「この関数をテストして」だけでは分岐coverage 100%に到達できない。どの分岐が抜けているか伝えて初めて、残りを埋められる。
LLMがテストを書けないのではない。LLMに何を書くべきか、どれだけうまく書けたかを伝える構造がないことが問題なのだ。
tsma:コマンド一つで回るテストレール
tsmaは、プロジェクトのすべての関数をインデックスし、テストの有無を検知し、coverageを計測し、LLMエージェントに正確なフィードバックを返すCLIツールだ。
エージェントが知るべきコマンドは一つだけ。
$ tsma next
このコマンド一つがループ全体を駆動する:
$ tsma next # テストのない次の関数を表示する
→ テストを書く
$ tsma next # 新しいテストを検知し、実行し、coverageを計測する
→ 100%? PASS、次の関数へ
→ <100%? 未カバー分岐をライン番号とともに表示する
$ tsma next # 修正されたテストを再計測する
→ 改善してもしなくても、DONEとして次へ
「All functions complete!」が出るまで繰り返す。
527個の関数で検証した
実際のGoプロジェクト(527個の関数)にtsmaを適用した。
| 結果 | 件数 | 割合 |
|---|---|---|
| PASS(100%分岐coverage) | 246 | 46.7% |
| DONE(best-effort) | 281 | 53.3% |
| TODO(未処理) | 0 | 0% |
246個の関数が分岐coverage 100%に到達した。残りの281個は100%に届かなかったが、可能な範囲までテストが書かれた。
なぜ100%に到達できない関数があるのか?
100%に到達する関数と到達しない関数
関数が100%分岐coverageに到達できるかは、依存性をどう受け取るかにかかっている。
インターフェース(mockable)– 100%達成可能:
type Handler struct {
svc AuthSvc // interface -- mockに差し替え可能
}
テストでmockを注入すれば、すべてのパスを制御できる:
svc := mocks.NewMockAuthSvc(ctrl)
svc.EXPECT().Login(...).Return(result, nil) // 成功パス
svc.EXPECT().Login(...).Return(nil, err) // 失敗パス
具象型(not mockable)– 100%不可能:
type Handler struct {
svc *service.SMSImportService // structポインタ -- 差し替え不可
}
実際の実装がDB、外部APIなどの内部依存性を持って動く。特定のエラーを発生させたり、特定の結果を返させたりできない。その結果に依存する分岐はユニットテストでは到達できない。
tsmaの対応: 未カバー分岐のフィードバック後にもう一度試みる。それでも到達できなければDONEとして受容する。これはツールの限界ではなく、コードのテスト可能性を反映している。Feathers(2004)が体系化したレガシーコードのジレンマ – 「コードを変えるにはテストが必要で、テストを入れるにはコードを変える必要がある」 – の解法は依存性の切断とインターフェース導入(DI)だ。インターフェースを導入すれば100%が可能になるが、それは元のコードを修正する作業だ。
フィードバックがLLMのテストを劇的に変える
tsmaの核心的価値は、インデックスでもcoverage計測でもない。未カバー分岐をライン番号で正確に伝えることだ。
フィードバックなし:
"ListContracts関数のテストを書いて"
→ LLMがhappy pathだけテスト
→ coverage 60~70%
フィードバックあり:
"ListContracts関数のテストを書いて"
→ coverage 65%(11/17)
→ UNCOVERED:
line 41 -- if params.Status != nil
line 44 -- if params.BuildingId != nil
line 70 -- if err != nil (CountSummary)
→ LLMがまさにその分岐をカバーするテストを追加
→ coverage 100%
同じLLMだ。違いはフィードバックの有無だけ。ライン番号3行が60%と100%を分ける。CoverUp(Pizzorno & Berger, 2024)は同じ原理を実証した。coverage分析結果をプロンプトに繰り返し挿入し、未カバー行にLLMの注意を集中させた結果、モジュール別line coverage中央値81%を達成した – フィードバックなしのbaselineより19pp向上。
エージェントが落ちても進捗は保存される
LLMエージェントは落ちる。トークン上限、ネットワークエラー、セッション切断。527個の関数を1セッションで全部処理することはできない。
tsmaは進捗を.tsma/session.jsonに永続保存する。
$ tsma status
527 functions
PASS: 246 (46.7%)
DONE: 281 (53.3%)
TODO: 0 (0.0%)
エージェントが200番目の関数で落ちたら? 新しいエージェントがtsma nextを実行すれば201番目から再開する。session.jsonがチェックポイントだ。
複数のエージェントが交代で作業しても衝突はない。関数単位でアトミックだ。
セッションはキャッシュ、ソースファイルが真実
tsmaの設計原則の一つ:sessionはキャッシュであり、ソースファイルがsource of truthだ。
テストファイルを削除すれば、session.jsonにPASSと記録されていても、その関数はTODOに戻る。セッションが現実と乖離しない。
原則:
session.jsonが"PASS"と言っても
テストファイルがなければ → TODO
ソースファイルが変わっていれば → 再計測対象
LLMエージェントへの指示
エージェントに必要な指示は6行だ:
1. tsma nextを実行
2. TODOなら -- 関数を読んでテスト作成
3. テスト失敗なら -- エラーを読んでテスト修正
4. 未カバー分岐が表示されたら -- その分岐をカバーするテスト追加
5. PASS/DONEなら -- 次の関数が自動で表示される
6. "All functions complete!"が出るまで繰り返す
エージェントが知るべきコマンドはtsma next一つだけ。残りはCLIが制約する。
列車と線路
バイブコーディングは列車だ。速い。しかし線路がなければ脱線する。
AIコーディングツールはすべて列車をもっと速くすることに集中している。より大きなモデル、よりスマートなエージェント、より良いプロンプト。しかし列車が速くなるほど、脱線の被害も大きくなる。
tsmaは線路だ。LLMがテストを生成し(Neural)、CLIが「ここまで」を定義する(Symbolic Constraint)。LLMの創造性はそのままに、結果の質は機械が強制する。
| 従来 | tsma | |
|---|---|---|
| テスト作成 | 人(遅い)またはLLM(無秩序) | LLMが作成、CLIが検証 |
| どこから? | 人が判断 | CLIが順序決定 |
| 品質確認 | 人がレビュー | CLIがcoverage計測 |
| フィードバック | なし | 未カバー分岐のライン番号 |
| 進捗追跡 | なし | session.json自動 |
LLMは自由に生成する。しかしtsma nextという線路の上でだけ走る。
言語サポート
| 言語 | インデクサ | テストランナー | Coverage |
|---|---|---|---|
| Go | go/ast | go test | go test -coverprofile |
| TypeScript | regex | npx vitest / npx jest | c8 / istanbul |
| Python | regex | pytest | coverage.py |
GoはASTパーサーで正確な関数抽出。TypeScriptとPythonは正規表現ベース。
生成ファイル(*_gen.go、*.pb.go)、テストファイル、vendor/node_modulesはインデックスから自動除外される。
インストールと実行
make install
cd your-legacy-project
tsma next
それだけだ。
MIT License. github.com/park-jun-woo/tsma
出典
- Schafer, M., Nadi, S., Eghbali, A., & Tip, F. (2023). An Empirical Evaluation of Using Large Language Models for Automated Unit Test Generation. IEEE Transactions on Software Engineering, 50(1), 85–105. arXiv:2302.06527
- Pizzorno, J. A., & Berger, E. D. (2024). CoverUp: Coverage-Guided LLM-Based Test Generation. arXiv preprint arXiv:2403.16218. arXiv:2403.16218
- Ryan, G., Jain, S., Shang, M., Wang, S., Ma, X., Ramanathan, M. K., & Ray, B. (2024). Code-Aware Prompting: A Study of Coverage-Guided Test Generation in Regression Setting using LLM. Proceedings of the ACM on Software Engineering (FSE 2024), 1(FSE), 951–971. ACM DL
- Lemieux, C., Inala, J. P., Lahiri, S. K., & Sen, S. (2023). CodaMOSA: Escaping Coverage Plateaus in Test Generation with Pre-trained Large Language Models. ICSE 2023, 951–963. ACM DL
- Feathers, M. C. (2004). Working Effectively with Legacy Code. Prentice Hall. ACM DL
- Besker, T., Martini, A., & Bosch, J. (2018). Technical Debt Cripples Software Developer Productivity. TechDebt 2018, 105–114. ACM DL
- Stripe. (2018). The Developer Coefficient. PDF
- U.S. Government Accountability Office. (2019). Information Technology: Agencies Need to Develop Modernization Plans for Critical Legacy Systems. GAO-19-471. GAO
- Tornhill, A., & Borg, M. (2022). Code Red: The Business Impact of Code Quality. TechDebt 2022, 11–20. arXiv:2203.04374
- Peng, S., Kalliamvakou, E., Cihon, P., & Demirer, M. (2023). The Impact of AI on Developer Productivity: Evidence from GitHub Copilot. arXiv:2302.06590
関連記事: Ratchet Pattern – エージェントに最後までやらせる方法 – tsmaの背景パターン。なぜ機械的検証がLLMの判断に勝るのか。
関連記事: モデルのIQよりフィードバック・トポロジー – なぜフィードバック構造がモデル性能より結果を左右するのか。
変更履歴
- 2026-05-14: 初版