ふくふくHukuhuku Inc.
EP.02Testing 13分公開: 2026-09-02

単体テストの粒度 ── AI に書かせると何が起きるか

指示なしに書かせると、網羅率は上がるのに壊れやすいテストが積み上がります。原因は粒度の選択にあります。何を単位とみなすかを決める判断軸を整理します。

#テスト#単体テスト#設計#AI開発
執筆 / 監修
松尾 亮合同会社ふくふく 代表社員

データ基盤・データパイプライン構築 / BI / 生成 AI 活用支援を専門とするエンジニア (28 年)。 本記事は AI 利用ポリシーに基づき、生成 AI の補助で執筆 → 人間が監修・編集して公開しています。

プロフィール詳細
シェア

「このファイルにテストを書いて」と指示すると、いまはかなりの精度でテストが出てきます。網羅率も上がります。ところが数か月運用すると、リファクタリングのたびに大量に赤くなる壊れた理由が読み取れない、という状態に陥っていることがあります。原因の多くは粒度、つまり何を一つの単位とみなすかの選択にあります。

この回は の粒度に絞ります。書く手間が下がったからこそ、粒度を人間が決めておく必要が出てきた、という話です。

1. 単位を「関数」に取ると壊れる

最も素直な発想は、関数ごとにテストを書くというものです。実際 AI に丸投げすると、この形になりがちです。関数の一覧が入力として与えられているので当然ではあります。しかしこの粒度には根本的な問題があります。関数の分かれ方は実装の都合で決まるという点です。

3つの関数に分けていた処理を2つに統合したとします。振る舞いは何も変わっていません。それでもテストは壊れます。外から見て何も変わっていないのにテストが赤くなるなら、そのテストは変更を守っているのではなく、変更を邪魔していることになります。

テストが壊れる理由は2種類ある

壊してはいけないものを壊した(正しい失敗)と、内部構造を変えただけ(無駄な失敗)。後者の比率が上がると、開発者はテストの赤を「またか」と受け流すようになります。粒度の設計は、この比率を下げる作業です。

2. 単位は「観測できる振る舞い」で取る

代わりに使える基準が、外から観測できる振る舞いです。呼び出し側にとって意味のある入出力の組を一つの単位とみなし、その内側がどう実装されていようとテストは関知しない。こうすると、実装の整理をしてもテストは緑のままです。

粒度の選択と、そのトレードオフ
粒度の取り方テストの安定性失敗時の分かりやすさ向く場面
関数ごと低い(実装に追随)高い純粋関数、アルゴリズム
振る舞いごと高い大半の業務ロジック
モジュールごと高い低い(範囲が広い)外部に公開する境界

「振る舞いごと」が万能というわけではありません。範囲が広がるぶん、失敗したときにどこが悪いのかは分かりにくくなります。純粋な計算処理やアルゴリズムのように、関数そのものが振る舞いと一致している場合は、関数ごとで何の問題もありません。

3. モックの使いすぎが粒度を壊す

粒度の問題は、 の使い方と直結しています。特に は「呼ばれ方」を検証する道具なので、使った瞬間にテストが内部構造を知っていることになります

内部構造に張り付いた例(呼び出し回数まで固定している)
Python
def test_apply_discount_calls_repository_twice(mocker):    repo = mocker.Mock()    repo.find.return_value = {"price": 1000}    service = PriceService(repo)
    service.apply_discount(item_id=1, rate=0.2)
    # 内部で repo.find を何回呼ぶかは実装の都合でしかない    assert repo.find.call_count == 2    repo.find.assert_called_with(1)

このテストは、キャッシュを入れて呼び出しを1回に減らしただけで失敗します。改善したのに赤くなるわけです。検証したかったのは割引後の金額のはずなので、そこだけを見れば十分です。

振る舞いだけを見る例(内部の呼び方には触れない)
Python
def test_apply_discount_returns_discounted_price():    repo = StubRepository({1: {"price": 1000}})    service = PriceService(repo)
    result = service.apply_discount(item_id=1, rate=0.2)
    assert result == 800

モック ではなく を使い、決まった値を返すだけにしています。呼ばれ方を検証していないので、内部をどう整理しても通ります。モックを使うべきなのは、「呼ばれたこと自体が仕様」である場合 ── 通知が飛ぶ、監査ログが残る、課金が発生する ── に限られます。

モックを置く場所の目安

外部の世界との境界にだけ置く。ネットワーク、時刻、乱数、ファイル、決済。ここは制御できないので差し替えが要ります。逆に、自分たちのコード同士の間にモックが増えているなら、粒度が細かすぎるサインです。

4. AI に書かせるときの指示の与え方

以上を踏まえると、指示に含めるべき情報が見えてきます。単位をどう取るかと、何を差し替えてよいかの2つです。これを言わないと、関数単位・モック多用の形に寄ります。

  • 検証したい振る舞いを言葉で先に書く — 「割引後の金額が正しいこと」。関数名ではなく結果で指定する
  • モックを置いてよい境界を明示する — 「外部APIと現在時刻のみ差し替え可。それ以外は本物を使う」
  • の対象を指定する — 「戻り値の金額を検証。呼び出し回数は検証しない」
  • を最小に保つよう指示する — 「そのテストに必要な項目だけ用意する」

書かせたあとに読む工程は省けません。ここは Claude Code 受託開発記 EP.09「レビュー文化」 と同じ話で、生成物の量が増えるほどレビューの設計が効いてきます。テストの場合、特に見るべきは アサーション が実質的な検証をしているかの一点です。

5. 弱いアサーションを見分ける

自動生成されたテストで最も多い問題が、実行はされるが何も検証していないというものです。 は上がるので、数値だけ見ると健全に見えます。

検証しているように見えて検証していない典型
よくある弱い検証何が問題かどう直すか
例外が出ないことだけ確認壊れた値を返しても通る戻り値そのものを検証する
戻り値が None でないことだけ確認型が合っていれば通る期待する値と比較する
ログが出たことだけ確認処理結果を見ていない状態の変化を検証する
長さだけ確認中身が入れ替わっても通る要素の内容まで比較する

これらを機械的に見つける手段として があります。コードにわざと小さな変更を加え、テストが落ちるかを確かめる手法です。落ちないなら、そのテストはその部分を守っていないと分かります。実行コストが高いので常時は回しませんが、重要な経路に一度かけると弱い箇所がはっきりします。

6. 粒度を決めるのは人間の仕事

この回の結論はひとつです。書く作業は任せられるが、単位の定義は任せられない。単位の取り方は、そのコードが事業の中でどういう役割を持つか、どの変更が起きうるかという文脈に依存します。文脈は指示として与えるしかありません。

逆に言えば、一度粒度の方針を決めてしまえば、その後の量産は非常に速いということでもあります。方針をプロジェクトの規約として書き残し、指示に含める。これが現時点で最も費用対効果の高いやり方だと考えています。

ここまでのまとめ

単位は関数ではなく観測できる振る舞いで取る。モック は外部との境界にだけ置き、内部には スタブ で足りる。自動生成されたテストは アサーション の強さだけは人間が読む。次回は、増えたテストの実行時間をどう抑えるかを扱います。

シェア

この記事の感想を教えてください

あなたの 1 クリックで、本当にこの記事は更新されます。「もっと詳しく」「続編希望」が一定数集まった記事は、 ふくふくが 実際に内容を拡充したり続編記事を公開 します。 送信したリアクションはお使いのブラウザに記録され、再カウントされません。

シリーズの外も探す:

まずは、現状を聞かせてください。

要件が固まっていなくて大丈夫です。現状診断と方針提案までを無料でお手伝いします。

無料相談フォームへ hello [at] hukuhuku [dot] co [dot] jp