tsma – レガシーコードの回帰防御線 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)24646.7%
DONE(best-effort)28153.3%
TODO(未処理)00%

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
Gogo/astgo testgo test -coverprofile
TypeScriptregexnpx vitest / npx jestc8 / istanbul
Pythonregexpytestcoverage.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: 初版