ブログ記事
リファクタリングでテストが壊れる理由:良いテストを作る4本の柱
コードを改善するたびにテストが壊れる——テストを増やしているのに開発スピードが落ちているなら、原因はテストの「量」ではなく「質」にある。
Vladimir Khorikov の『単体テストの考え方/使い方』は、テストを「バグ発見ツール」から「プロジェクトが長期間成長し続けるための投資」へと定義し直す観点から書かれた一冊だ。テストコードもメンテナンスが必要なコストである——この前提に立つことで、テストに投資する価値の判断軸が根本的に変わる。
1. 良いテストを評価する4本の柱
Khorikov は、テストの質を「感覚」で評価するのではなく、次の4軸で客観的に測ることを提唱している。
- 退行に対する保護: バグを検出できるか。コード量や処理の複雑さに比例して価値が高まる。
- リファクタリング耐性: 内部の実装を変えてもテストが誤って失敗しないか(偽陽性を出さないか)。
- 迅速なフィードバック: テストの実行速度が開発のリズムを壊さないか。
- 保守のしやすさ: テストコード自体が読みやすく、セットアップが簡単か。
4つすべてを同時に最大化することはできないが、「リファクタリング耐性」だけはバイナリ(あるかないか)の問題だと著者は言う。これが欠けているテストは、リファクタリングのたびに失敗して開発者を誤誘導する。本物のバグを見逃す温床になるため、値が「ゼロ」になった時点でそのテストは純粋なコストになる。
flowchart TD
T["良いテストの4本の柱"] --> P1["退行に対する保護\nバグを検出できるか"]
T --> P2["⚠️ リファクタリング耐性\n偽陽性を出さないか\n(バイナリ:ゼロにしてはいけない)"]
T --> P3["迅速なフィードバック\n実行速度が速いか"]
T --> P4["保守のしやすさ\n読みやすく変更しやすいか"]
style T fill:#e8f4fd,stroke:#4a9eda
style P2 fill:#fce8e8,stroke:#e53935
2. 「振る舞い」を検証することがリファクタリング耐性を生む
テストが壊れやすい根本原因の多くは、「中身の仕組み(実装の詳細)」をテストが知りすぎていることにある。
例えば「この関数の内部で、クラス A のメソッドが呼ばれたか」を検証するテストは、クラス A を別の実装に置き換えた瞬間、機能が正しく動作していても失敗する。これが偽陽性だ。
対照的に「最終的に何が出力されたか」「システムとして何が達成されたか」という「観測可能な振る舞い」だけを検証するテストは、内部の改善に影響されない。Khorikov が「古典学派(デトロイト学派)」のアプローチとして支持するのはこの考え方で、テストはエンドユーザーの視点から見える結果に絞るべきだという立場だ。
3. モックの使用を「管理外の依存」に限定する
一部の流派では、テスト対象クラスが使う協力オブジェクトをすべてモックに置き換えるアプローチをとる。テストが書きやすくなる反面、クラス間の呼び出しパターンという実装の詳細にテストが密結合してしまう問題がある。
Khorikov が推奨するのは、モックの使用をデータベース・外部 API・メッセージキューなど「管理外のプロセス外依存」に限定することだ。同じプロセス内のクラス同士は本物を使ってテストを組み立てる。こうすることで、内部設計を大きく変えても既存のテストが通り続ける構造が作れる。
4. テストが書きにくいなら、それは設計への警告だ
「このクラスはテストが書きにくい」と感じる原因は、テストスキルの不足より設計上の問題である場合が多い。ビジネスロジックがデータベースアクセスや外部 API 呼び出しと同居していると、テストはセットアップが複雑になり、実行も遅くなる。
本書では「関数型コア・命令的シェル」というアーキテクチャパターンが紹介されている。副作用を持たない純粋なドメインロジック(関数型コア)と、I/O を担うシェル(命令的シェル)を分離することで、コアの部分はモックなしで高速に、ケースを漏らさずテストできる。この分離は、テスト品質の問題を解決しながら同時にアーキテクチャを改善する。
こんな人に向いている
「テストは書いているが、それが本当に役立っているか自信がない」「テストが重くなって書くのが苦になってきた」という実務経験2年以上のエンジニアに向いている。テストの哲学と設計原則を整理したい人、テストコードのレビューでより具体的なフィードバックをしたいリードエンジニアにも向いている。単にテストの書き方ではなく「何を、なぜテストするか」という問いに正面から答える一冊だ。
逆に、テストフレームワークの基本的な使い方をこれから学ぶ段階の人には早い。また本書の主張はかなり率直で、モックを積極的に使う開発スタイル(いわゆるロンドン学派)を実践しているチームにとっては、自分たちの流儀を正面から批判される内容でもある。既存の流儀を補強してくれる本を探しているなら、期待とは違う読書になる。
主張の強さと「理想と現実のギャップ」
本書への批判として知っておきたいのは、推奨される「関数型コア・命令的シェル」や副作用の分離が、理論としては筋が良い一方、フレームワークに深く依存した既存の大規模システムへそのまま適用するには相応の工数と設計力が要る、という点だ。読後に「明日から全部この形に直す」と考えるより、新規に書く部分・変更頻度の高いコアから少しずつ原則を持ち込むのが現実的な使い方になる。
読んだ後に変わること
本書を読むと、テストを「書く義務」ではなく「設計の道具」として捉えるようになる。テスタビリティを意識するだけで、コードの結合度や責任の分割が自然と改善されていく。チームでテスト文化を育てたいエンジニアが、なぜそのテスト設計が良いかを説明する語彙と理由を得られる一冊だ。
次に進む本
単体テストの質を判断できるようになったら、関心に応じて進む先が分かれる。サービス境界をまたぐ API の品質を守りたいなら、統合・契約・E2E というピラミッド上位の配分を扱う『Web APIテスト技法』へ。テストのない既存コードに原則を適用したいなら、依存を断ち切る継ぎ目の作り方を示す『レガシーコード改善ガイド』が本書の原則の適用編になる。
実務での最初の一歩としては、直近のリファクタリングで壊れたテストを 1 つ選び、「実装の詳細を検証していないか」を 4 本の柱で診断してみるのが手頃だ。
筆者の体験から
リードを任されてから、内部の実装を少し変えただけでテストが大量に赤くなる状況に悩まされていた。モックを多用する書き方がチームに定着しており、モック対象の中身を書き換えるたびにテストも書き直す羽目になっていた。この本で古典学派とロンドン学派の違いを整理して読んだとき、自分たちが無意識にロンドン学派寄りだったと気づき、モックの対象をデータベースや外部APIなど管理外の依存に絞る方向へレビュー基準を変えた。
関数型コア・命令的シェルの分離は理屈として納得できても、既存のフレームワークに乗ったコードへそのまま持ち込むのは難しく、新しく書く部分から少しずつ取り入れる妥協に落ち着いた。印象に残っているのは、あるリファクタリングで内部クラスの呼び出し順を検証していたテストがまとめて落ちた時のことだ。機能自体は正しく動いており、単に実装を差し替えただけだった。4本の柱で見ればリファクタリング耐性がゼロのテストだったと気づき、以降は最終的な出力を検証する形に書き直すようにしている。
DevBookPath のマップで確認する
この本の学習パス上の位置づけ・前後の読書順は、DevBookPath のグラフで辿れます。
本記事のリンクには Amazon アソシエイト等の広告が含まれる場合があります。リンク経由の購入で運営者に紹介料が支払われることがあります。
この記事を共有
この地図を共有