Gemini CLI の Subagents でテストコード生成チームを組んでみる

· Google

Gemini CLI には「Subagents」という機能があります。これは、特定の専門分野に特化したエージェントをあらかじめ定義しておき、メインエージェントから呼び出して仕事を任せられる仕組みです。

ここでは、Subagents を使って「ソースコードからテストコードを自動生成するチーム」を組み上げる手順について紹介したいと思います。

Subagents とは

Subagents は、メインエージェントから呼び出される子エージェントです。それぞれが独立した文脈で動き、特定の専門分野に特化した仕事をこなしてくれる、頼もしい仲間というイメージです。

僕が思うに、Subagents の良いところは大きく 3 つあります。

  • 専門性の高いタスクをそれぞれのエージェントに任せられる
  • タスクごとにコンテキストが分離されるので、メインエージェントの文脈が汚れない
  • 互いに依存しないタスクは並列で実行できるので、トータルの待ち時間が短くなる

ちなみに、Subagents から別の Subagents を呼び出すことはできません。あくまでメインエージェントが全体を束ねる形になります。この制約はシンプルでわかりやすいので、僕としては好きだなと思っています。

テストコード生成を分解する

さて、いきなりエージェントの定義ファイルを書き始める前に、「テストコード生成」という仕事をどう分解するかを考えてみたいと思います。

テストコードを書こうと思ったとき、僕たちは普段こういう順番で作業しているはずです。

  1. 対象ファイルを読み込んで、テストすべき関数や入出力を整理する
  2. 外部依存があれば、モックやスタブを用意する
  3. 正常系(ハッピーパス)のテストを書く
  4. 異常系・境界値のテストを書く

実は、2, 3, 4 のステップは並列に進められる仕事です。先に 1 で得られた解析結果を全員で共有しておけば、あとはそれぞれが独立して書き進められます。これは Subagents の特徴とぴったりかみ合う構成かなと思います。

そこで、今回は 4 つの Subagent を作ることにします。

  • test-analyzer — 対象ファイルを解析する
  • test-mock-builder — モック・スタブを生成する
  • test-writer-happy — 正常系テストを書く
  • test-writer-edge — 異常系・境界値テストを書く

それぞれの担当範囲を明確にしておくのが、Subagents をうまく機能させるコツかなと思っています。

4 つの Subagent を定義する

Subagent は、Markdown ファイルとして .gemini/agents/ ディレクトリに配置します。フロントマターでメタ情報を指定し、本文に役割と振る舞いを書く、というのが基本の形です。

test-analyzer.md

まずは解析担当の Subagent です。

---
name: test-analyzer
description: ソースコードを解析し、テスト対象の関数・クラスの一覧、依存関係、入出力の型情報を構造化して返すエージェント。
tools:
  - read_file
  - read_many_files
  - glob
  - grep_search
  - list_directory
---

あなたはコード解析の専門家です。
指定されたソースファイルを読み込み、テストを書くために必要な情報を抽出してください。

## 解析項目

### 関数・メソッド一覧

各関数について以下を整理すること:

- 関数名
- 引数(名前、型、デフォルト値の有無)
- 戻り値の型
- 副作用の有無
- エラーをthrowする条件

### 依存関係

- importしている外部モジュール
- importしている内部モジュール
- モックが必要になりそうな依存

### テスト観点の提案

- 正常系で確認すべきパターン
- 境界値・エッジケース
- エラーハンドリングで確認すべきケース

## 出力ルール

- Markdown形式で構造化して出力すること
- コードの中身を丸ごとコピーせず、テストに必要な情報だけを抽出すること
- 不明な型や推測が必要な箇所は「要確認」と明記すること

このエージェントは「テストを書く」ことはせず、後続のエージェントが必要とする情報を整理することに専念します。役割を明確に絞ることが、出力品質を上げるうえで大事かなと思います。

test-mock-builder.md

次にモック生成担当です。

---
name: test-mock-builder
description: 外部依存(API、DB、ファイルI/Oなど)のモックやスタブを生成するエージェント。
tools:
  - read_file
  - read_many_files
  - glob
  - grep_search
---

あなたはモック・スタブ生成の専門家です。
ソースコードの解析結果を受け取り、テストに必要なモックコードを生成してください。

## 対象

- 外部APIクライアント(fetch, axios等)
- データベース接続・クエリ
- ファイルシステム操作(fs)
- 環境変数(process.env)
- 日時(Date, Date.now())
- サードパーティライブラリ

## モック方針

- jest.mock() / jest.spyOn() を基本とする
- モックデータはリアルな値に近い具体的な値を使う
- 成功レスポンスと失敗レスポンスの両方を用意する
- 型安全なモックを心がける

## 出力ルール

- モックのセットアップコードを出力する
- 各モックに「何をモックしているか」のコメントを付ける
- テスト側でどう使うかの利用例も1つ示す

ここで僕がポイントだと思うのは「モックデータはリアルな値に近い具体的な値を使う」というルールです。LLM に丸投げすると { id: 1, name: 'test' } のような無味乾燥な値を量産しがちなので、ここを明文化しておくと出力の質がぐっと上がるかなと思います。

test-writer-happy.md と test-writer-edge.md

正常系と異常系は、別々の Subagent に分けます。一つのエージェントに両方やらせるよりも、観点を分けたほうがそれぞれの観点に集中して、質の高いテストが出てくるからです。

---
name: test-writer-happy
description: 正常系(ハッピーパス)のテストコードを生成するエージェント。
tools:
  - read_file
  - glob
  - grep_search
---

あなたは正常系テストの専門家です。

## テスト方針

- 各関数の「典型的な使い方」を網羅する
- 引数の代表的な値で期待される出力を検証する
- 戻り値の型と構造を検証する

## コーディング規約

- テストフレームワークはプロジェクトに合わせる
- AAA パターン(Arrange / Act / Assert)で構造化する
- 各テストは独立して実行できること

異常系担当は次のような感じになります。

---
name: test-writer-edge
description: 異常系・境界値・エッジケースのテストコードを生成するエージェント。
tools:
  - read_file
  - glob
  - grep_search
---

あなたは異常系テスト・境界値テストの専門家です。

## テスト方針

- 境界値: 0, 1, -1, 最大値, 最小値, 空配列, 空文字列
- 型の境界: null, undefined, NaN, Infinity
- エラーケース: 不正な引数、想定外の型
- 状態の境界: 空の状態、上限に達した状態

## 出力ルール

- 正常系テスト(test-writer-happy)との重複を避ける
- 各テストにコメントで「なぜこのケースを検証するのか」を一行書く

「正常系との重複を避ける」と一行書いておくだけで、両者が同じテストを生成してしまうのを防げます。Subagent 同士は互いの出力を直接知らないので、こういう「住み分けのルール」を明文化しておくのが大事なのかなと思っています。

オーケストレーター GEMINI.md

4 つの Subagent を作っただけでは、それぞれを順番にどう呼び出すかは決まりません。そこで、メインエージェント側に「テストコードを書いてと言われたら、この手順で動く」というルールを持たせます。プロジェクトディレクトリの GEMINI.md に書いておくと、そのプロジェクト内でだけ有効になります。

## テストコード生成

ユーザーから「テストを書いて」「テストを生成して」と依頼されたら、
以下の手順でテストコードを生成する。

### Step 1: 解析

@test-analyzer を起動し、対象ファイルの解析結果を得る。

### Step 2: 並列生成

解析結果を元に、以下のサブエージェントを起動する:

- @test-writer-happy: 正常系テストを生成
- @test-writer-edge: 異常系・境界値テストを生成
- @test-mock-builder: 必要なモック・スタブを生成
  (解析結果に外部依存がある場合のみ)

各サブエージェントには解析結果とソースファイルのパスを渡すこと。
可能であれば、これら3つのサブエージェントを並列で実行する。

### Step 3: 統合

全サブエージェントの出力を1つのテストファイルにまとめる。

### Step 4: テスト実行

統合したテストファイルを実際に実行する。
失敗したテストがあれば原因を分析し、修正する。
全テストが通るまで修正を繰り返す(最大3回まで)。

### Step 5: レポート

最終結果を報告する:

- 生成したテスト数
- カバレッジ(取得できる場合)
- 修正が必要だったテストとその理由

ポイントは、Step 2 で「並列で実行する」と明示している点です。Subagent 同士に依存関係がない場合、並列化することで待ち時間を大幅に減らせるからです。

もちろん、「最大 3 回まで修正を繰り返す」のような上限も入れておくのが安全かなと思います。LLM は失敗続きでも妙に頑張ってしまう傾向があるので、適度な歯止めは必要です。

題材ファイルを用意する

さて、実際に動かしてみるための題材として、こんな src/calculator.ts を用意します。

export function divide(a: number, b: number): number {
  if (b === 0) {
    throw new Error("Division by zero");
  }
  return a / b;
}

export function average(numbers: number[]): number {
  if (numbers.length === 0) {
    throw new Error("Empty array");
  }
  return numbers.reduce((sum, n) => sum + n, 0) / numbers.length;
}

export async function fetchRate(currency: string): Promise<number> {
  const res = await fetch(`https://api.example.com/rates/${currency}`);
  if (!res.ok) {
    throw new Error(`Failed to fetch rate: ${res.status}`);
  }
  const data = await res.json();
  return data.rate;
}

ちょうど良い具合に「純粋関数(divide)」「配列を扱う関数(average)」「外部 API を叩く非同期関数(fetchRate)」が揃っているので、4 つの Subagent それぞれが活躍できるサンプルかなと思います。

実行してみる

準備が整ったので、Gemini CLI を起動し直して試してみます。

$ cd <プロジェクトディレクトリ>
$ gemini

起動できたら、以下のように依頼してみてください。

src/calculator.ts ファイルのテストコードを作ってください。

すると、メインエージェントが GEMINI.md のルールに従って動き始めます。まず test-analyzer が走り、解析結果が返ってきます。続いて test-mock-buildertest-writer-happytest-writer-edge が並列で動き、それぞれの出力が集まったところで一つのテストファイルに統合されます。

最後に Gemini CLI が npm test を実行し、すべてのテストが通っていることを確認してくれます。src/__tests__/calculator.test.ts のような場所にテストファイルが配置されていれば成功です。

並列で動いている 3 つのサブエージェントの様子を眺めていると、それぞれが独立した出力を返してくる感じが見ていて気持ち良かったです。出来上がったテストファイルも、describe('正常系', ...)describe('異常系・境界値', ...) のブロックがきれいに分かれていて、役割分担がそのまま構造に表れているのが印象的でした。一人のエージェントに丸投げしたときよりも、観点の抜け漏れが少ないかなと思っています。

まとめ

今回は、Gemini CLI の Subagents を使ってテストコード生成の専門家チームを組む手順について紹介してみました。

ポイントを振り返っておきます。

  • 仕事を「分解できる粒度」まで分けてから Subagent を設計する
  • 並列で動かせる部分は並列で動かすようにオーケストレーターに書く
  • Subagent 同士の住み分けルールは明文化しておく
  • 修正回数の上限のような歯止めをオーケストレーターに入れる

Subagents は、エージェントの世界における「マイクロサービス的な分業」を手軽に試せる機能だなと思っています。テストコード生成以外にも、たとえばリリースノート作成、ドキュメント整備、コードレビューなど、応用先はたくさんあるかなと思います。

本エントリが、Gemini CLI を自分なりに活用するための参考になれば幸いです。