ふくふくHukuhuku Inc.
EP.06Testing 12分公開: 2026-09-06

スナップショットテストの功罪 ── 通っているのに何も守っていない状態

出力を丸ごと記録して比較する手法は、書くのが最も楽で、形骸化するのも最も速い。差分をそのまま承認する運用に陥る構造を分解し、それでも使う場合の条件を整理します。

#テスト#スナップショット#アンチパターン#レビュー
執筆 / 監修
松尾 亮合同会社ふくふく 代表社員

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

プロフィール詳細
シェア

は、出力をまるごと記録し、次回以降その記録と差分がないかを見る手法です。書くのが圧倒的に楽で、一行で導入できます。前回の の実装としても自然な形です。

同時に、最も形骸化しやすい手法でもあります。この回は、なぜそうなるのかを構造として分解し、それでも使う場合の条件を整理します。全否定はしません。条件付きで有効な道具です。

1. 形骸化は「更新が楽」から始まる

スナップショットの利点は、差分が出たときに記録側をワンコマンドで更新できることです。この利便性が、そのまま形骸化の入口になります。

  1. 1コードを変更し、テストを流す
  2. 2スナップショットに差分が出て、赤くなる
  3. 3変更したのだから差分が出るのは当然、と考える
  4. 4更新コマンドを実行して緑に戻す
  5. 5差分の中身は読んでいない

3番目の判断が曲者です。多くの場合は本当に当然なので、この推論は大半のケースで正しい。正しいからこそ習慣化し、混ざり込んだ本物の不具合も一緒に承認されます。ここが との決定的な違いです。単体テストで期待値を書き換えるときは「期待値そのもの」を打ち込むので、何を変えたかを意識せざるを得ません。スナップショットの更新にはその契機がなく、手を動かさずに期待値が書き換わります

検証していないのに緑になる

この状態のテストは、実行はされているが何も守っていません。しかも には計上され、テスト本数としても数えられます。健全に見えることが、この問題の最も厄介な点です。

2. 差分が大きいほど読まれない

もうひとつの構造的な問題が、記録の粒度です。画面全体の HTML や API レスポンス全体を記録すると、記録は数百行になります。小さな仕様変更でも差分は広範囲に及びます。

記録の粒度と形骸化の関係
記録の粒度差分の読みやすさ検知できる範囲形骸化しやすさ
出力全体(画面・レスポンス丸ごと)低い広い高い
注目する部分だけを抜き出して記録高い狭い低い
項目を明示して個別に検証最も高い定義した範囲最も低い

検知できる範囲と読みやすさは逆向きに効きます。広く記録すれば検知力は上がりますが、読まれなくなれば検知力はゼロになります。実効的な検知力は「範囲 × 読まれる確率」であって、範囲だけを見ても意味がありません。テストの本数やカバレッジといった指標がこの掛け算を捉えられないことが、スナップショットの問題を見えにくくしています。

3. 使ってよい条件

以上を踏まえると、スナップショットが有効に働く条件がはっきりします。

  • 差分が小さく収まる粒度で記録している — 数行から数十行。読める量であること
  • 差分がレビューの対象に入っている — 記録ファイルもプルリクエストの差分として人が見る
  • 出力が本来安定している対象である — 頻繁に変わる画面には向かない
  • 変動要素が除去されている — 時刻やIDが混ざっていれば毎回落ちる(EP.5 参照)

2番目が実務上いちばん効きます。記録ファイルを差分として見せるだけで、無確認の更新はかなり減ります。逆に、記録ファイルをレビュー対象から外していると、ほぼ確実に形骸化します。

記録ファイルを読ませる仕組みにする

記録をテストコードと同じ場所に置き、差分表示から除外しない。これだけで運用が変わります。除外設定に入れてしまうと「変更されたことすら見えない」状態になり、手法として成立しなくなります。

4. 抜き出して記録する

粒度の問題は、記録する前に絞ることで解決できます。出力全体ではなく、壊れてはいけない部分だけを抜き出して記録します。

全体ではなく、意味のある部分だけを記録する
Python
def summarize_invoice(html):    """帳票 HTML から、壊れてはいけない値だけを抜き出す。"""    doc = parse(html)    return {        "total": doc.select_one("[data-field=total]").text,        "tax": doc.select_one("[data-field=tax]").text,        "line_count": len(doc.select("[data-row]")),        "has_seal": doc.select_one("[data-field=seal]") is not None,    }

def test_invoice_summary(snapshot):    html = render_invoice(order_id=1)    # 記録されるのは4項目だけ。差分が出れば意味が読み取れる    assert summarize_invoice(html) == snapshot

こうすると、記録は4項目になります。差分が出れば 「合計金額が変わった」 と即座に読めます。HTML 全体を記録していた場合、同じ変化は数百行の差分のどこかに埋もれます。

この形にすると、スナップショットである必要すら薄れてきます。4項目なら直接 を書いても手間は変わりません。実際、ここまで絞れたなら明示的に検証するほうが、何を守っているかがコードから読めるぶん優れています。

5. 向いている対象、向いていない対象

とはいえ、スナップショットが素直に有効な領域もあります。出力が構造的で、安定していて、手で書き下すには量が多いものです。

向いている例 — スキーマの変更を検知する
YAML
# 記録されるのは構造だけ。差分が出れば「型が変わった」と即座に読めるOrderResponse:  type: object  required: [order_id, total, status]  properties:    order_id: { type: string }    total:    { type: integer }    status:   { type: string, enum: [pending, paid, cancelled] }

スキーマのように構造そのものが仕様である対象は、スナップショットと相性がよい例です。差分が出れば「必須項目が減った」「列挙値が増えた」と直接読めます。逆に、差分を読んでも意味が汲み取れない対象 ── 整形済みの HTML や自然文 ── は向きません。差分が意味を持つかどうかが、実用上の分かれ目です。

スナップショットの向き不向き
対象向き不向き理由
設定ファイルの生成結果向く構造的で安定。全項目を手で書くのは冗長
APIの型・スキーマ定義向く変わったら気づきたい。差分が意味を持つ
データ変換の出力(少数の代表行)向く件数を絞れば差分が読める
画面の HTML 全体向かない変更頻度が高く差分が大きい
エラーメッセージの文言向かない変わってよいものを固定してしまう
LLM の生成文向かない毎回変わる。EP.7 で別の方法を扱う

6. 判断の基準

この回の結論は、「楽に書ける」を選定理由にしないということです。書く手間が下がった以上、楽さを理由に選ぶ意味はもうありません。何を守りたいかを先に決め、それが読める形で残るかで選ぶ。

そのうえでスナップショットを選ぶなら、粒度を絞り、記録をレビュー対象に含める。この2つが守れないなら、明示的な検証に置き換えたほうが、結果として保守も楽になります。判断に迷ったら、その記録ファイルを最後に人が読んだのはいつかを思い出してみてください。思い出せないなら、そのテストはすでに機能していません。

テストの価値は、書く速さではなく、壊れたときに何を教えてくれるかで決まる。

ここまでのまとめ

形骸化は更新の手軽さ差分の大きさから生まれる。有効に使う条件は、粒度を絞ること記録をレビュー対象に含めること。絞りきれたなら明示的な アサーション のほうが優れている場合が多い。次回からは、そもそも出力が毎回変わる対象 ── LLM の検証に入ります。

シェア

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

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

シリーズの外も探す:

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

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

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