良いテストの判断とは——『単体テストの考え方・使い方』第2部を読んでまとめてみた

良いテストの判断とは——『単体テストの考え方・使い方』第2部を読んでまとめてみた

はじめに

こんにちは。株式会社アイスリーデザインの芹田です。

第1部では、「なぜ単体テストをするのか」「単体とは何か」「読みやすいテストの型」を整理しました。

▼リリース前は気が重い——『単体テストの考え方・使い方』第1部を読んでまとめてみた
https://tech.i3design.jp/unit-testing-guide-part1/

次は、「このテストは、良いの? 悪いの?」という疑問にぶつかります。

網羅率でも本数でもなく、判断できる物差しが欲しい。そこを深掘りしてくれるのが第2部です。

本記事は、その読書メモの第2部を、次の流れで自分なりに噛み砕いてまとめたものです。
物差し → モックで検証する相手 → 検証の手法 → 投資先の選び方

理解しやすいよう言い換えた箇所もあるため、厳密な用語とは異なる表現があるかもしれませんが、同じように「良い/悪い」の判断軸が欲しい方のきっかけになれば幸いです。

物差し 〜良いテストを構成する4本の柱〜

第4章の核心は、「良いテスト」を感覚ではなく、4つの観点で語ることです。

現場で言うと
リグレッション(デグレ)への保護バグをどれだけ検出できるか
リファクタリング耐性設計を直しても、テストが誤って落ちないか
迅速なフィードバックテストがどれだけ速く返ってくるか
保守のしやすさ読んで・直して・実行するのが楽か

「リグレッションへの保護」は、第1部で「リグレッション(デグレ)防止」と書いた価値と同じ観点です。ここでは第4章の柱名に合わせて「保護」と表記します。

本書では、「4本のうち1つでも欠けているテストには価値がない」と言っています。(正直、ここはかなり厳しい基準だと感じました…)
言い換えると、テストの価値は4つの掛け算のような関係で、どれかがゼロなら全体もゼロ、という考え方です。完璧である必要はありませんが、ゼロの柱を残さない、という意味だと理解しました。

一方、リグレッションへの保護・リファクタリング耐性・迅速なフィードバックの3つは、1本のテストで同時に最大化できません
だからこそ、テストの種類ごとに役割を分ける「テストピラミッド」の考え方が成り立ちます。単体は速く狭い範囲を守り、E2E は広く遅い、といった分担です。

モック 〜検証対象を間違えると壊れる〜

第5章では、前章の4本柱の観点から、モックの使い方を見ます。

モックが壊れやすくなるのは、道具そのものが悪いからではありません。「どの相手とのやりとりを検証するか」という対象選びを誤ることが原因です。

たとえば「注文を確定したら、確認メールを送る」という機能を想像してください。

適切な対象(外部との約束)不適切な対象(内部の実装詳細)
検証内容「確認メールを送る」という外部への約束が守られたか注文クラスが、内部の別クラスのメソッドをこの順番で呼んだか
なぜそう言えるかアプリの外(メールサービス)とのやりとりだからアプリの中のクラス同士のやりとりだから
モックで見てよいかよい(「送られたか」を見てよい)よくない(設計を直すたびに呼び出し順が変わり、テストだけが落ちやすい)

前者はビジネス上の約束(観察可能な振る舞い)です。後者は実装の詳細です。第4章の「リファクタリング耐性」を自分で壊してしまうのが、後者のパターンです。

書籍では、この違いを次の2つに分けています。

種類何かモックで検証してよいか
システム間コミュニケーションアプリと外部(メール、決済、他サービス)とのやりとりよい(外から見える約束)
システム内コミュニケーションアプリ内のクラス同士のやりとりよくない(実装の詳細)

ここでいう「よい」は、外部サービスの内部実装まで縛る話ではありません。アプリが外部に対して持つ契約のうち、観察できる最小限を検証する、という意味です。

あわせて、置き換えたあとの「検証する/しない」も押さえておきます。コマンド/クエリは操作の分類、モック/スタブは置き換える道具の分類です。

テストダブル扱い
状態を変える・起こす操作(コマンド)モック呼ばれたか・契約に必要な範囲でどう呼ばれたかを検証する
データを返すだけの操作(クエリ)スタブ値を返すだけ。呼び出しは検証しない

コマンドは「何かを変える・起こす操作」、クエリは「データを返すだけの操作」です。スタブは、テスト条件を作るために値を返す置き換えなので、「スタブが呼ばれたか」まで検証してはいけません。

まとめると、

  • モックは外部との境界で使うこと。
  • クラス同士の内部の呼び方まではモックで縛らないこと。
  • スタブとのやりとりは検証しないこと。

ということでした。

第1部で「共有依存だけをテストダブルに置き換える」と整理した方針が、ここで「どこを検証するか」まで具体的になってきます。

手法 〜何を見て「成功」と判定するか〜

第6章では、検証の仕方が3つに整理されます。

手法何を検証するか
出力値ベーステスト返ってくる値
状態ベーステスト実行後に残る状態
コミュニケーションベーステスト他オブジェクトとのやりとり

(本書でいう「古典学派」は、第1部で触れた「単体=1つの振る舞い」と捉え、共有依存だけをテストダブルに置き換える立場のことです。)

古典学派の考え方では、デフォルトの優先順位として「出力値ベース > 状態ベース > コミュニケーションベース」と整理されています。これは「状態ベースは禁止」という意味ではなく、選べるなら出力を優先する、という話です。

注意点はシンプルです。

  • 状態ベースでは、プライベートな状態を公開してまで検証しない
  • コミュニケーションベースは、アプリの境界を超え、外部から確認できる副作用があるときに限る

第5章の「境界だけモックする」と、ここで1本につながります。

正直なところ、第6章は技術寄りの話が多く、私にとっては難しく感じた箇所もありました。それでも、「何をもって成功と言えるのか」を3つに分けて考えるだけでも、既存テストの見直しには十分使えそうだと思いました。

投資先 〜単体テストをどこに寄せるべきか〜

物差し・検証する相手・手法が揃っても、「どこに書くか」がずれると、テストは増えても価値は上がりません。第7章の主題はここです。

コードは、次の2軸で4種類に分けられます。

  • コードの複雑さ/ドメインにおける重要性
  • 協力者オブジェクトの数(テスト対象が依存している他のクラスやモジュールの数)
種類複雑さ/ドメイン重要性協力者オブジェクト単体テストの扱い
ドメインモデル/アルゴリズム高い少ないいちばん価値が高い。寄せる本命
コントローラ低め多い単体より統合寄り
過度に複雑なコード高い多いリファクタリング対象(ロジックと難しい依存を分ける)
取るに足らないコード低め少ない優先度は低い。書かない選択もあり

単体テストの価値が最も高いのは、複雑またはドメインで重要なコードです。
本書では、次の方針を推奨していました。

  • ドメインで重要なコードに協力者をたくさん含めない。
  • 複雑さや重要性が上がるほど、協力者の数を減らす。

もう1つの核心は、第1部の落とし穴①と地続きです。

質の悪いテストを作るくらいなら、その種のテストは全く作らない方がよい。

「書かない」は放棄ではなく、価値の低いテストを無理に増やさない、という意味で、負債になるものは増やさないほうが良いということです。

第7章も設計の話が深く難解でしたが、「単体テストは全部に均等に書くものではない」という点は、しっかりと理解できました。

第2部の学び

学び
第4章 物差し4本柱で良い/悪いを語る。
第5章 モック境界だけモック。内部は縛らない。
第6章 手法出力を優先。やりとりの検証は境界外に限る。
第7章 投資先ドメイン/アルゴリズムに寄せる。悪いテストなら書かない。

第1部の「振る舞い単位・共有依存のみテストダブル化」は、第2部を読み終えた時点でも維持できる、というのが私の現時点の考えです。

まとめ・すぐに使えるチェックリスト

第2部では、良い/悪いを測る物差しと、モック・手法・投資先の選び方を見てきました。

私にとって非常に参考になる内容で、これまでより自信を持って品質活動を進められると感じました。

一方で、技術的な記述が多く、読み解くのに苦労しました…。理解しきれていない部分もあるので、またしばらくしたら読み返したいと思っています。

ここまでが本書の前半です。後半では、ここでの理解がさらに深まりそうだと感じています。

単体テストの良し悪しに迷う場面は、私を含めて少なくないと思います。そうした方が現場ですぐ見直せるポイントを、古典学派を前提に方針/運用/技術へ分けてまとめたので、参考にしていただければ幸いです。

単体テストの方針(何をテストするか)

テストの狙いが正しいかを見る観点。コードを読めなくても、PM・QA が判断に加われます。

  • 4本柱のうち、ゼロになっている柱がないか(どの柱を優先し、どこを妥協したかを説明できるか)
  • ビジネスロジックや重要なルール(ドメインモデル/アルゴリズム)にテストが寄っているか
  • 取るに足らないコードや、価値の低いテストを無理に増やしていないか
  • 質の悪いテストなら、書かない(または統合テストへ移す)選択をしているか

単体テストの運用観点(コードを読まずに判断できる)

テストの外側から見える情報だけで判断できる観点。技術に詳しくなくても確認できます。

  • テスト名から、ビジネス上の振る舞い(何を検証しているか)がわかるか
  • 実行時間が短く、開発中に繰り返し回せる速度を保てているか
  • CI に組み込まれ、繰り返し実行されているか
  • 設計変更のたびに、振る舞いを変えていないのにテスト修正が増えていないか

単体テストの技術的チェック(コードを読んで判断する)

テストコードの中身を読める人が見る観点。

  • モックがシステム間(外部との境界)に限定されているか
  • スタブとのやりとりを検証していないか
  • 検証は出力値ベースを優先し、コミュニケーションベースは境界外の副作用に限っているか
  • 状態検証のために、プライベートな状態を公開していないか
  • 過度に複雑なコードを、そのまま単体テストで無理にカバーしていないか

本記事は、Vladimir Khorikov 著『単体テストの考え方・使い方』(マイナビ出版)第2部を読んだメモをもとに執筆しています。

リリース前は気が重い——『単体テストの考え方・使い方』第1部を読んでまとめてみた
Cursor Commands活用術:単体テスト自動生成プロンプト

ABOUT US
Yosuke_Serita
テレビ制作会社、通信業界、人材業界、IT業界にて、営業や管理、育成と幅広い業界と業種を経験。 QAに強いPMを目指し、2023年冬から株式会社アイスリーデザインにジョイン。 DXパートナーとして、品質の観点からお客様の課題解決プロジェクトを推進できるよう日々研究中。