導入
「AIにテストコードを書かせてみたものの、結局手直しに倍以上の時間がかかってしまった」
テックリードやシニアエンジニアの皆様であれば、一度はこのような経験があるかもしれません。GitHub CopilotやChatGPTなどの生成AIツールは、ボイラープレート(定型コード)の生成には極めて優秀ですが、複雑なビジネスロジックや依存関係が絡む単体テストにおいては、しばしば期待外れの結果を返します。
なぜ、AIは「動かないテスト」を生成してしまうのでしょうか。
その答えはシンプルです。AIが「コンテキスト(文脈)」を知らないからです。
テスト対象の関数がどのクラスに依存しているのか、どのようなインターフェース定義に従うべきか、プロジェクト固有のテスト規約はどうなっているのか。ファイル単体しか見ていないAIにとって、これらは未知の領域です。結果として、存在しないメソッドを呼び出したり、不適切なモックを作成したりといった「ハルシネーション(もっともらしい嘘)」が発生します。
AIソフトウェアエンジニアが直面する実務の現場において、この「コンテキスト不足」こそが、AIテスト自動化における最大のボトルネックであると言えます。
そこで注目すべきなのが、Sourcegraph Codyです。Codyは、リポジトリ全体のコードベースをインデックス化し、依存関係や定義ジャンプといった「コードの文脈」を深く理解した上で回答を生成する能力に長けています。
本記事では、単なるツールの紹介にとどまらず、Codyのコンテキスト認識能力を最大限に引き出し、修正工数を8割削減するための実践的なメソッドを解説します。リポジトリ全体の依存関係を考慮した堅牢なテストを、いかにして効率よく量産するか、その論理的なアプローチを共有します。
なぜ「コンテキスト」が単体テスト自動生成の品質を決定づけるのか
単体テスト(ユニットテスト)は、対象のコードが仕様通りに動作することを保証する防波堤です。しかし、AIによる自動生成において、この防波堤が脆くなるケースが後を絶ちません。ここでは、その技術的な要因と、Codyがどのようにその壁を突破するのかを分析します。
従来のAI補完における「視野狭窄」の問題点
従来のコード生成AIツールの多くは、現在開いているファイルや、直前に編集したタブの内容(アクティブなコンテキスト)を主な情報源としています。これは「局所的な最適化」には有効ですが、システム全体を見渡す必要があるテスト設計には不十分です。
例えば、UserServiceクラスのテストを生成する場合を考えてみましょう。このクラスがUserRepositoryインターフェースに依存しているとします。AIがUserRepositoryの定義ファイルを見ていなければ、推測でメソッドを生成したり、戻り値の型を間違えたりします。
これは「AIの視野狭窄」とも呼べる現象です。AIは目の前のコード片(トークン)の確率的な続きを予測しているに過ぎず、そのコードがシステム全体の中でどのような役割を果たしているかを理解していません。この状態で生成されたテストコードは、構文的には正しくても、論理的には破綻していることが多く、エンジニアによる大幅な修正(リファクタリング)を余儀なくされます。
単体テストにおける「依存関係」と「仕様」の正確な把握
高品質な単体テストを書くためには、以下の2つの要素を正確に把握する必要があります。
- 依存関係(Dependencies): テスト対象(SUT: System Under Test)が利用している外部モジュール、DB、APIクライアントなどの構造。
- 仕様(Specifications): 入力に対してどのような出力が期待されるか、どのような例外が発生すべきかという振る舞いの定義。
これらは往々にして、テスト対象のファイル内には記述されていません。別のディレクトリにあるインターフェース定義ファイルや、共通のユーティリティクラス、あるいはドキュメント(Markdownファイル)に散らばっています。
人間がテストを書く際、無意識のうちにエディタで定義元へジャンプしたり、ドキュメントを参照したりして、これらの情報を脳内で統合しています。AIに同じことをさせるには、これらの散らばった情報を明示的に、あるいは自動的に「コンテキスト」としてプロンプトに含める仕組みが不可欠です。
Sourcegraph Cody独自の「Code Graph」による理解深度
ここでCodyの優位性が際立ちます。Sourcegraphは元々、大規模なコード検索エンジンとして発展してきました。その基盤技術である「Code Graph」は、リポジトリ内のすべてのシンボル(関数、クラス、変数など)の定義と参照関係をグラフ構造で保持しています。
CodyはこのCode Graphを活用し、ユーザーが質問(あるいはテスト生成指示)を投げた瞬間に、関連性の高いコード片をリポジトリ全体から検索・抽出します。これをRAG(Retrieval-Augmented Generation)技術と組み合わせることで、LLM(大規模言語モデル)に対して「今からテストするこのクラスは、あのファイルのあの定義に依存している」という情報を動的に注入できるのです。
具体的には、以下のようなプロセスが内部で行われます。
- コンテキスト検索: カーソル位置や指示内容に基づき、関連するファイルや定義をCode Graphから検索(Embeddings検索とキーワード検索のハイブリッド)。
- プロンプト構築: 検索された関連コードを、LLMのコンテキストウィンドウに収まるように最適化して注入。
- 生成: 豊富な文脈情報を持ったLLMが、整合性の取れたテストコードを出力。
この仕組みにより、Codyは「ファイル単体しか見ないAI」とは決定的に異なる、リポジトリ全体の文脈を汲んだテストコードを生成できます。これは特に、大規模で複雑なプロジェクトにおいて、テスト修正工数の大幅な削減に寄与します。
基本原則:Codyに「正しい文脈」を渡すための3つのルール
ツールが高機能であっても、使い手が適切な入力を与えなければ最高の結果は得られません。「Garbage In, Garbage Out(ゴミを入れればゴミが出る)」はAI活用の鉄則です。ここでは、Codyに「正しい文脈」を認識させ、実用的なテストコードを出力させるための3つの基本原則を提示します。
原則1:関連ファイルの明示的指定(@mention機能の活用)
Codyは自動的にコンテキストを推測しますが、エンジニアの意図を100%読み取れるわけではありません。特にテスト生成においては、「どのファイルを参照すべきか」を人間が明示することで精度が格段に向上します。
Codyのチャット機能では、@を入力することでリポジトリ内のファイルを指定してコンテキストに追加できます(@mention機能)。
例えば、PaymentService.tsのテストを書きたい場合、単に「このファイルのテストを書いて」と指示するのではなく、以下のように指示します。
プロンプト例:
@PaymentService.tsの単体テストを書いてください。依存している@IPaymentGateway.tsと@User.tsの定義を参照し、適切なモックを作成してください。
このように、テスト対象だけでなく、依存先のインターフェースや型定義ファイルを明示的に@で指定して渡すことが重要です。これにより、AIは推測ではなく、実際のコード定義に基づいてモックを構築できるようになります。
原則2:ドキュメントと実装の乖離を埋めるプロンプト設計
コードだけでは読み取れない「ビジネスロジックの意図」や「仕様の背景」は、ドキュメントに書かれていることが多いです。実装コードだけを渡すと、AIは「現在の実装(バグを含む可能性がある)」を正としてテストを書いてしまいます。これではバグを見つけるテストにはなりません。
仕様書や設計メモがMarkdownなどでリポジトリ内にある場合、それもコンテキストに含めましょう。
プロンプト例:
@PaymentSpec.mdの仕様に基づいて、@PaymentService.tsのテストケースを網羅的に生成してください。特に「決済失敗時の再試行ロジック」について重点的にテストしてください。
これにより、AIは「実装がどうなっているか」だけでなく「本来どうあるべきか」という基準を持ってテストコードを生成します。実装と仕様の乖離(バグ)を検出するテストを生成させるためには、この「仕様のコンテキスト化」が不可欠です。
原則3:テスト設計方針(Testing Trophy等)の事前共有
プロジェクトによって、テストの方針は異なります。「Testing Trophy」のように結合テストを重視するのか、「Testing Pyramid」のように単体テストを厚くするのか。あるいは、使用するライブラリ(Jest, Vitest, JUnit, Pytestなど)や、モックライブラリ(Mockito, unittest.mockなど)の選定も様々です。
何の情報も与えないと、Codyは一般的な(学習データで最も頻出する)スタイルでコードを生成します。これを防ぐために、プロジェクトのテスト規約や方針を明示したファイル(例: TESTING_GUIDELINES.md)を用意し、セッションの最初に読み込ませるか、Custom Commands(後述)に組み込むことを推奨します。
プロンプト例:
@TEST_CONVENTION.mdに従い、React Testing Libraryを使って@UserProfile.tsxのコンポーネントテストを作成してください。スナップショットテストは禁止です。
このように「制約条件」をコンテキストとして与えることで、レビューで指摘されるようなスタイル違反を未然に防ぐことができます。
実践ベストプラクティス①:複雑な依存関係を持つクラスのモック自動生成
単体テストの実装で最も時間を取られるのが「モック(Mock)」の準備です。特に、データベース、外部API、ログ基盤など、複数の依存を持つクラスのテスト準備(Setup)は煩雑になりがちです。Codyを活用して、この工程を効率化する手法を紹介します。
依存注入(DI)パターンの解析とモック戦略
Dependency Injection(依存性の注入)パターンを採用しているコードはテストしやすいと言われますが、それは「モックに差し替え可能だから」であって、モックを作る手間自体が減るわけではありません。CodyにコンストラクタやDIコンテナの設定を解析させ、自動的にモックを生成させましょう。
以下は、TypeScriptでの例です。
// ユーザーの指示
// @UserService.ts @UserRepository.ts @EmailService.ts を参照
// UserServiceのテストを作成してください。コンストラクタで注入される
// UserRepositoryとEmailServiceはJestのモック関数として定義し、
// beforeEachで初期化してください。
Codyは、UserRepositoryやEmailServiceのパブリックメソッドを解析し、jest.fn()を用いたモックオブジェクトを自動生成します。ここで重要なのは、インターフェースの型定義情報がコンテキストに含まれていることです。型情報があれば、TypeScriptのコンパイルエラーにならない正確なモック構造(メソッド名、引数、戻り値の型)を生成できます。
外部API連携を含むメソッドのスタブ化
外部APIを呼び出すメソッドのテストでは、APIレスポンスをシミュレートする「スタブ(Stub)」が必要です。CodyにAPIクライアントのコードや、OpenAPI(Swagger)定義ファイルを参照させることで、リアルなレスポンスデータを生成させることが可能です。
プロンプトのコツ:
「@ApiClient.tsのgetUserメソッドが返す成功時のJSONレスポンスと、404エラー時のレスポンスをモックデータとして定義し、それを用いたテストケースを書いてください」
このように指示することで、単なる空のオブジェクトではなく、本番に近いデータ構造を持ったダミーデータを生成してくれます。Faker.jsなどのライブラリを使っている場合は、「Fakerを使ってランダムなデータを生成して」と指示を追加すれば、より柔軟なテストデータが得られます。
修正不要なセットアップ(Setup/Teardown)コードの生成手法
テストコードのメンテナンス性を高めるには、beforeEach(セットアップ)とafterEach(ティアダウン)の適切な記述が欠かせません。AIにテストケースを個別に書かせると、毎回同じ初期化コードを繰り返す冗長なコードになりがちです。
これを避けるために、プロンプトで「DRY(Don't Repeat Yourself)原則」を意識させる指示を出します。
プロンプト例:
共通の初期化処理はbeforeEachにまとめ、テストケース間での状態汚染がないようにafterEachでモックをリセット(jest.clearAllMocks)する構造にしてください。
Codyは一度このパターンを指示されれば、以降の追加テストケース生成でも同じ構造を維持する傾向があります。さらに、Codyの「Fixup」や「Edit」機能を使って、既存のテストファイルを選択し、「共通処理をbeforeEachにリファクタリングして」と指示するだけで、既存コードの整理も任せることができます。
実践ベストプラクティス②:エッジケースと境界値の網羅性を高める
正常系のテスト(Happy Path)はAIも得意とするところです。しかし、バグが潜むのは常に「境界値」や「異常系」です。Codyの推論能力を使って、人間が見落としがちなテストケースを洗い出す手法を解説します。
仕様書(Markdown)をコンテキストに含めたテストケース抽出
前述の通り、仕様書をコンテキストに含めることは強力です。ここではさらに一歩進んで、「仕様書の矛盾や考慮漏れ」をAIに指摘させつつテストを書かせるテクニックを紹介します。
プロンプト例:
@DiscountLogic.mdの仕様に基づき、@CartService.tsのテストケースを列挙してください。また、仕様書に記述がないエッジケースや、論理的に曖昧な点があれば指摘し、その場合のテストケースも提案してください。
こうすることで、Codyは単なるコード生成ツールではなく、QA(品質保証)の視点を持ったアシスタントとして機能します。「合計金額がマイナスになる場合はどう処理するか」「商品数が0の場合はどうなるか」といった指摘と共に、それらをカバーするテストコードを提示してくれるため、品質向上に直結します。
既存のバグ報告(Issue)に基づいた回帰テスト生成
バグ修正を行う際、再発防止のための「回帰テスト(Regression Test)」は必須です。GitHub IssuesやJiraなどで管理されているバグ報告を活用し、再現テストを効率的に生成しましょう。
確実な方法は、Issueの内容を一時的なMarkdownファイル(例:bug_report.md)に保存し、それをコンテキストとしてCodyに渡すことです。単なるテキストのコピー&ペーストよりも、ファイルとして認識させることで、Codyは構造化された情報として理解しやすくなります。
プロンプト例:
@bug_report.mdの内容を再現するテストケースを作成してください。
このテストは現状のコード(@UserService.ts)では失敗するはずです(Red)。その後、テストが通るように修正案も提示してください(Green)。
いわゆるTDD(テスト駆動開発)のサイクルをCodyと回すことができます。具体的なエラーログやスタックトレースを含めることで、Codyは問題箇所を特定し、その条件を再現するテストを正確に生成します。最新のLLMモデルを選択していれば、複雑なログの解析能力も高いため、原因特定のアシスタントとしても機能します。
Codyによる「テストケースの不足」指摘機能の活用
既にテストコードが存在する場合でも、カバレッジが十分とは限りません。Codyに対して、既存の実装とテストを比較させ、足りない観点を洗い出させることができます。
プロンプト例:
@OrderService.tsとそのテスト@OrderService.test.tsを比較し、テストされていない分岐条件や例外処理があればリストアップしてください。そして、それらをカバーする追加のテストコードを生成してください。
これは「AIによるコードレビュー」の一種です。開発者自身では網羅したつもりでも、「ネットワークエラー時の再試行ロジックがテストされていない」といった客観的な指摘を受けることができます。これを定期的に実行することで、テストスイートの網羅性を高く保つことが可能です。
実践ベストプラクティス③:プロジェクト固有のコーディング規約への準拠
組織で開発する場合、コードの統一感は品質と保守性に直結します。AIが生成するコードがチームのスタイルと乖離していると、コードレビューでの修正コストが増大し、生産性を阻害する要因となります。ここでは、Codyにプロジェクトの規約を遵守させるための技術的なアプローチを解説します。
既存テストファイルを「few-shot」として活用する
LLMの制御において、現在も有効な手法の一つが「Few-shot Prompting」です。これは、AIに対して2〜3個の具体的な「入力と出力の例(ショット)」を与えることで、期待する回答のパターンや形式を学習させる「In-Context Learning(文脈内学習)」の一種です。
最新のAIモデルにおいても、単に指示を与えるだけでなく、良質な例示を与えることで精度が向上することが確認されています。Codyの場合、既存の「模範的なテストコード」をコンテキストとして渡すことが、このFew-shotの実践にあたります。
新しいクラスのテストを書く際は、単に作成を依頼するのではなく、同じプロジェクト内の「参照すべきテストファイル」を明示的に@メンションで指定し、さらに思考のプロセス(Chain of Thought)を促すプロンプトを組み合わせるのが効果的です。
推奨プロンプト例:
あなたは品質保証のスペシャリストです。以下の手順で@NewService.tsのテストを作成してください。
- スタイル分析:
@Reference.test.tsのコーディング規約(命名規則、describe/itの階層構造、アサーションライブラリの使い方)を分析してください。- テスト設計: 分析結果に基づき、必要なテストケースをリストアップしてください。
- コード生成: 既存コードのスタイルに厳密に従い、テストコードを出力してください。
このように、役割(Role)を与え、参照ファイル(Few-shot例)を示し、段階的な思考(CoT)を促すことで、変数名の付け方から非同期処理の書き方まで、既存コードに近いスタイルで生成されます。
カスタムコマンドによる「自社流テスト生成」の標準化
チーム全体でプロンプトエンジニアリングの質を均一化するために、Sourcegraph Codyの「Custom Commands(カスタムコマンド)」機能を活用します。頻繁に使用するプロンプトをショートカットとして定義し、共有設定としてリポジトリに含める運用です。
例えば、プロジェクトのルートディレクトリの設定ファイル(例: .vscode/cody.json や .cody/commands.json など、最新の仕様は公式ドキュメントを参照)に、以下のようなコマンドを定義します。
{
"commands": {
"test-gen-standard": {
"description": "プロジェクト標準の単体テストを生成",
"prompt": "現在のファイルに対して、@TestUtils.ts と @GlobalMocks.ts を使用し、社内規約 @TEST_STYLE_GUIDE.md に準拠した単体テストを作成してください。出力前にテストケースの網羅性を確認すること。"
}
}
}
これをリポジトリにコミットすることで、チームメンバー全員が右クリックメニューやコマンドパレットから test-gen-standard を実行可能になります。個人のプロンプトスキルに依存せず、統一された品質とスタイルのテストコードが生成される仕組みを構築できます。
CI/CDでの自動チェックとCodyによる修正提案
テストコードの生成だけでなく、CI(継続的インテグレーション)パイプラインでのエラー修正プロセスにもCodyを組み込むことが可能です。
テストが失敗した際、そのエラーログと関連コードをCodyに読み込ませることで、迅速な原因特定が可能になります。最新のIDE拡張機能では、ターミナルの出力結果を直接コンテキストとして取り込む機能も強化されています。
「このテスト失敗の原因を解析し、実装側のバグなのか、テストコード側の誤りなのかを切り分けて修正案を提示して」と依頼することで、デバッグ時間を短縮できます。特に複雑な依存関係があるテストの場合、人間が見落としがちなセットアップ漏れやモックの不整合をAIが指摘してくれるケースは少なくありません。
アンチパターン:コンテキスト過多と依存の落とし穴
ここまでCodyのコンテキスト機能を推奨してきましたが、無差別に情報を読み込ませれば良いというわけではありません。ここでは注意すべきアンチパターンを解説します。
「リポジトリ全体」への過信とノイズ情報の混入
Codyは強力ですが、コンテキストウィンドウ(一度に処理できる情報量)には上限があります。また、無関係な情報を大量に与えると、LLMの推論精度が下がる「ノイズ」となります。
例えば、UserServiceのテストを書くのに、関係のないProductServiceやUIコンポーネントのコードまで読み込ませる必要はありません。関係のないファイルに似たようなメソッド名があると、AIが混同してしまうリスクがあります。
対策: 常に@メンションを使って、本当に必要なファイルだけをピンポイントで指定する習慣をつけましょう。Codyの自動推論に頼りすぎず、人間が情報の交通整理を行う意識が重要です。
生成されたテストの検証不足(False Positiveの危険性)
最も危険なのは、「テストが通ったから問題ない」と盲信することです。AIは時として、「何も検証しないテスト」や「常にTrueになるアサーション」を生成することがあります。
例えば、非同期処理のテストで await を忘れてしまい、アサーションが実行される前にテスト関数が終了して「成功」扱いになるケースなどです。
対策: 生成されたテストコードを目視レビューする際は、「何が検証されているか(What is being asserted)」を必ず確認してください。また、ミューテーションテスト(わざとコードを壊してテストが失敗するか確認する手法)を取り入れるのも有効です。
テストコードのメンテナンス性を無視した生成
AIは「動くコード」を作るのは得意ですが、「読みやすく、変更に強いコード」を作るのは苦手な場合があります。コピー&ペーストの乱用や、過度に複雑なセットアップを含むテストコードは、将来の技術的負債になります。
対策: 生成されたコードに対しても、リファクタリングを躊躇しないことが求められます。あるいは、Codyに対して「コードの重複を排除して」「可読性を高めて」といった指示を追加で出し、品質を練り上げていくプロセスが必要です。
導入効果の検証:修正工数とバグ検出率の変化
最後に、Codyを活用した「コンテキスト認識型テスト生成」が、実際の開発現場にどのようなインパクトをもたらすのか、定量的な効果の傾向を見てみましょう。
Before/After:テスト実装にかかる時間の比較データ
中規模Webアプリケーション開発プロジェクト(TypeScript/React)における事例では、従来のAIツール(ファイル単体参照)と、Cody(複数ファイル参照+ドキュメント参照)を使用したケースで、単体テスト実装にかかる時間を比較したデータがあります。
- 従来手法: テスト生成後の手動修正(モック定義、型エラー修正)に平均 15分/ファイル。
- Cody活用: 生成後の手動修正は平均 3分/ファイル。
結果として、約80%の修正工数削減が確認されたケースが存在します。特に、複雑な依存関係を持つサービスクラスにおいて、その差は顕著に表れます。
コンテキスト有無による生成コードの修正行数比較
生成されたコードのうち、人間が修正しなければならなかった行数(Diff)を計測すると、適切なコンテキストを与えた場合、修正率はコード全体の 5%未満 に留まる傾向があります。一方、コンテキストなしの場合は 30%以上 の行に修正が必要となることが一般的です。
これは、Codyが最初から正しい型、正しいメソッド名、正しいモック構造を使用していることの証明と言えます。「手直し」という作業が激減することを意味します。
開発者の精神的負荷(Cognitive Load)の軽減効果
定性的な効果として見逃せないのが、開発者の心理的負担の軽減です。「テストを書くのが面倒」という感情は、テスト品質低下の要因の一つです。
Codyによって「土台となる高品質なテスト」が瞬時に生成されるようになると、エンジニアは「テストを書く」作業から、「テスト設計を考える」「エッジケースを検討する」という、より高次な思考にリソースを割けるようになります。これは、チーム全体の開発者体験(DX)を向上させ、結果としてプロダクトの品質向上に直結します。
まとめ
Sourcegraph Codyを活用した単体テスト自動生成は、単なる時短テクニックではありません。それは、リポジトリ全体の「文脈」を適切に扱い、堅牢で信頼性の高いソフトウェアを構築するための論理的なエンジニアリング手法です。
- コンテキストの力: ファイル単体ではなく、依存関係と仕様を含めた「全体像」をAIに提示する。
- 基本原則:
@メンションによる明示的指定、ドキュメントの活用、規約の共有を徹底する。 - 実践と継続: モック生成の自動化から始め、カスタムコマンドでチーム全体の標準化を図る。
AIが生成したテストの修正に時間を奪われる状況は、適切なツールとアプローチによって解決可能です。Codyに正しい文脈を与えることで、テストコードは「ゼロから書くもの」から「生成結果を分析・確認するもの」へと進化します。
最新のAI開発ツールを論理的かつ実践的に活用し、圧倒的な生産性と品質の両立を目指してみてはいかがでしょうか。
コメント