本ページはプロモーションが含まれています

IT全般

単体テストのロンドン学派と古典学派、2つの流派を比較する

トム

・都内自社開発企業勤務/Javaバックエンドエンジニア
/Java歴10年以上 ・首都圏在住30代
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎 詳細なプロフィール

「テストを書いたせいで、コードの修正が何倍も大変になった」。以前の私は、締め切り間際に叫びたい衝動を必死で抑えていました。単体テストを導入して品質が上がるどころか、テストの修正作業に追われて進捗が止まったからです。実はこれ、多くのエンジニアが通る「単体テストの罠」に他なりません。

この記事では、テストの定義から、設計思想の根幹である「ロンドン学派」と「古典学派」の違いまでを整理します。私はこれまで10年近く開発に携わってきましたが、当初はモックを使いすぎる「ロンドン学派」に傾倒し、テストの保守性の低さに絶望しました。現在は、保守性の高い「古典学派」を軸に、必要に応じてモックを組み合わせるスタイルに落ち着いています。

この記事を読めば、単体テストの基礎が身につくだけでなく、自分のプロジェクトに最適なテスト戦略を選べるようになります。テストが開発の重荷になっている方や、効率的な設計を目指す初心者から中級者の方にとって、道標となるはずです。テストは正しく書けば最強の味方ですが、間違えると開発速度を削ぐだけの負債となります。その境界線がどこにあるのか、私の失敗談を交えて解き明かしましょう。

単体テストの「隠れた本質的な問題」は、実装の詳細とテストが密結合になる事態です。この問題を放置すると、リファクタリングのたびにテストが壊れ、最終的にテストを誰も実行しなくなるという最悪の結末を迎えます。

単体テストとは何かを一言で説明

単体テストは、プログラムの最小単位が期待通りに動くか確認するプロセスです。開発者が自分の書いたコードに対して責任を持つための「最小の保証」と言い換えられます。バグを早期に発見するだけでなく、コードが仕様を満たしているか即座にフィードバックを得る役割を果たします。

私は新人の頃、テストは品質管理部門が行うものだと勘違いしていました。しかし、実際にコードを書く側がテストを行わないと、複雑なロジックの正当性を証明できません。単体テストは、開発者が自信を持ってコードをリリースするための、精神安定剤のような側面も持ち合わせています。

単体テストの「単体」とはどこまでを指すのか

「単体」の定義は、実は開発者の間で意見が分かれるポイントです。一般的には、クラスやメソッドといったコード上の最小構成要素を指します。しかし、最近では「振る舞いの単位」として捉える考え方が主流になりつつあります。

1つのクラスをテストするのか、それとも1つの機能をテストするのか。この解釈の違いが、後述する学派の対立を生むきっかけとなります。私は「外部から見た1つの動作」を単位とするのが、最も管理しやすいと感じています。

単体テストが求められる本当の理由(品質・設計・安心感)

単体テストの真の価値は、リファクタリングを可能にする安心感にあります。コードを綺麗に書き直した際、テストが成功すれば、機能が壊れていないと断言できるからです。

また、テストが書きにくいコードは、設計に問題があるサインです。依存関係が複雑すぎたり、1つのメソッドが多機能すぎたりする場合、テストコードを書くのが苦痛になります。テストは、あなたのコードが健全な設計であるかを測るための、最も正直なセンサーとして機能するのです。

単体テストと他のテストは何が違うのか整理

単体テストは、実行速度と分離のレベルにおいて、他のテストと一線を画します。数秒以内に何千ものテストが完了するスピード感が、開発のリズムを作ります。これに対し、他のテストはより広い範囲を対象にするため、実行に時間がかかる傾向があります。

私は過去に、単体テストを軽視してシステムテストばかりに頼ったプロジェクトを経験しました。その結果、バグが見つかるのが数日後になり、修正コストが跳ね上がってしまいました。テストの階層構造を理解し、適切な場所で網を張る意識が、スムーズな開発には欠かせません。

結合テスト・システムテストとの役割の違い

結合テストは、複数のユニットが正しく連携するかを確認する段階です。システムテストは、実際のユーザー操作に近い形で、システム全体が要件を満たすか検証します。

単体テストが「部品の精度」を見るのに対し、これらは「組み立て後の動作」をチェックします。ネジが1本曲がっていても、車は走るかもしれません。しかし、後で事故を起こさないためには、最初の段階でネジの不備を見つけるのが単体テストの役割です。

単体テストにだけ期待してはいけないこと

単体テストが万全でも、システム全体のバグがゼロになるわけではありません。画面のレイアウト崩れや、ネットワークの遅延による不具合は、単体テストの範疇外です。

「単体テストで100%の網羅率を達成したから安心だ」と考えるのは非常に危険です。あくまでロジックの正しさを保証するツールであり、ユーザー体験をすべてカバーするものではないと心得ましょう。複数のテスト手法を組み合わせるバランス感覚が、プロのエンジニアには求められます。

単体テストにはどんな種類があるのかを全体像で理解する

単体テストのやり方は、大きく分けて「何を確認するか」で2つの手法に分類できます。結果としての「状態」を見るのか、プロセスとしての「呼び出し」を見るのか。この違いを理解すると、テストコードの書き方がガラリと変わります。

多くの初心者は、関数の戻り値だけを確認するテストから始めます。しかし、データベースの更新やメール送信といった外部への作用を確認する場合、別の手法が必要になります。全体の構成を把握することで、状況に応じた最適なテスト手法を選択できるようになるはずです。

状態検証と振る舞い検証という2つの考え方

状態検証は、テスト対象を動かした後の「最終的な値」が正しいかを確認する手法です。例えば、1+1の結果が2になっているかをチェックするような、シンプルで直感的なテストを指します。

一方、振る舞い検証は、テスト対象が「他のオブジェクトを正しく呼び出したか」を確認します。特定のメソッドが1回だけ呼ばれたか、正しい引数が渡されたかを検証するのです。前者は結果を、後者は過程を重視するアプローチであり、どちらも重要な役割を持っています。

テストダブル(モック・スタブ)が必要になる理由

テスト対象がデータベースや外部APIに依存している場合、そのままではテストが実行できません。そこで登場するのが、本物の代わりを務める「テストダブル」です。

スタブはあらかじめ決められた値を返し、モックは呼び出しの内容を記録します。これらを使うことで、外部環境に左右されず、いつでもどこでもテストを動かせるようになります。私は、不安定な外部APIに悩まされていた頃、スタブの便利さに何度も救われました。

ロンドン学派とは「依存を切り離す」単体テストの考え方

ロンドン学派は、テスト対象のクラスが依存するすべての要素をモックに置き換える流派です。「モック主義者」とも呼ばれ、テスト対象を完全に孤立させることを重視します。

この手法の最大のメリットは、テストの失敗原因が明確になる点です。依存先をすべて偽物にしているため、テストが落ちたなら、それは100%テスト対象のクラスに原因があります。私は大規模なチームで働いていた際、この「責任分解の明確さ」に大きなメリットを感じていました。

ロンドン学派が前提としている設計思想

ロンドン学派は、オブジェクト指向における「役割とメッセージ」の交換を重視する設計に基づいています。各クラスが独立した部品として動き、それらがどう繋がるかをテストで表現しようとします。

この思想では、ボトムアップで部品を作るのではなく、トップダウンで全体の流れを定義していきます。テストを書きながら、必要な依存先のインターフェースを導き出す「Outside-In TDD」と非常に相性が良いです。設計が固まっていない段階では、この柔軟性が大きな武器になります。

モック中心のテストが向いているケースと注意点

複雑なビジネスロジックが、多くの外部サービスと連携するようなケースではロンドン学派が輝きます。メール送信や決済処理など、実際には動かしたくない処理を簡単にバイパスできるからです。

しかし、注意点として、テストが「実装の詳細」に依存しすぎる傾向があります。メソッド名を変えるだけでテストが壊れるため、リファクタリングの邪魔になる場合も多いです。私はかつて、モックを使いすぎて、コードを少し変えるたびに100個のテストを手修正する羽目になりました。

古典学派とは「実装を信じる」単体テストの考え方

古典学派は、可能な限り本物のオブジェクトを使ってテストを行う流派です。「デトロイト学派」とも呼ばれ、状態検証をメインに据えます。

テスト対象が依存しているクラスも、それが同じメモリ内で動くなら本物を使います。これにより、複数のクラスが組み合わさった状態での正しさを検証できるのが強みです。私は、テストの信頼性と保守性を両立させたい場合、まずはこちらのアプローチを検討します。

古典学派が重視するテストの粒度

古典学派では、1つのクラスを「単体」とするのではなく、1つの「振る舞い」を単位とします。そのため、複数のクラスにまたがるテストになるケースも珍しくありません。

大切なのは「外部から見て何が起きたか」であり、内部でどのクラスが呼ばれたかは問いません。この考え方のおかげで、内部構造を大幅に変えても、外部への出力が変わらなければテストは成功し続けます。リファクタリング耐性が非常に高いことが、古典学派の最大の魅力です。

実オブジェクト中心のテストが向いているケース

計算処理やデータ変換など、副作用が少なく完結しているロジックのテストに最適です。本物のオブジェクトを使うため、セットアップが簡単で、テストコード自体が読みやすくなります。

ただし、データベースや共有ファイルなど、実行速度を落としたり状態を共有したりする要素は、古典学派でもテストダブルを使います。すべてを本物にするのではなく、「共有依存」だけを切り離すのが賢いやり方です。私はこの「現実的な妥協」こそが、実務において最も効率的だと考えています。

ロンドン学派と古典学派を具体例で比較してみる

では、同じ「ユーザー登録処理」を2つの学派でテストすると、どう変わるのでしょうか。ロンドン学派では、データベース保存用のクラスをモックにし、「保存メソッドが呼ばれたか」を確認します。対して古典学派では、テスト用のデータベース(あるいはインメモリDB)に実際にデータが保存されたかを検証します。

この違いは、単なる書き方の好みの問題ではありません。テストが何を保証し、何を守ってくれるのかという「哲学」の違いです。比較することで、自分のプロジェクトがどちらの恩恵をより多く受けられるかが見えてくるはずです。

同じ処理を2つの学派でテストすると何が変わるのか

ロンドン学派のテストは、非常に細かく、コードの1行1行をなぞるような内容になります。依存先との契約を細かくチェックするため、設計のガイドラインとしては優秀です。

一方で古典学派は、もう少し大まかな「結果」にフォーカスします。ユーザーが登録された後にリストを取得して、その中に名前があるかを見るような、ユーザー視点に近いテストになります。結果として、古典学派のテストは、実装を大幅に変えても壊れにくい「タフなテスト」になります。

テストの壊れやすさ・読みやすさの違い

ロンドン学派のテストは、内部実装と密接に結びついているため、非常に壊れやすい傾向があります。メソッドの引数を1つ追加しただけで、関係ないテストが全滅することさえあります。

読みやすさの面でも、古典学派に軍配が上がることが多いでしょう。本物のオブジェクトを使う古典学派は、実際のコードの使用例(ドキュメント)として機能しやすいからです。私が保守を引き継いだプロジェクトで、モックだらけのテストコードを見て絶望した経験は一度や二度ではありません。

現場ではどちらを選ぶべきか迷わなくなる判断軸

結局、どちらの学派が良いのかという問いに、唯一の正解はありません。プロジェクトの特性や、チームの習熟度によって選ぶべき選択肢は変わります。

私は現在、新規プロジェクトでは「古典学派」をデフォルトにしています。しかし、レガシーコードの保守や、外部APIとの連携が複雑な箇所では、迷わずロンドン学派のテクニックを借ります。この柔軟な使い分けこそが、現場で求められる現実的な解だと確信しています。

プロジェクト規模・チーム構成による向き不向き

大規模なチームで、各エンジニアが担当する部品を厳密に切り分けたい場合は、ロンドン学派が機能します。他人の書いたコードの詳細を知らなくても、インターフェースさえ決まればテストが書けるからです。

逆に、少人数でスピード感を重視し、頻繁にリファクタリングを行うなら、古典学派が圧倒的に有利です。テストの修正コストが低いため、開発のテンポを崩さずにコードの改善を続けられます。自分のチームがどちらのスピード感を求めているか、一度立ち止まって考えてみてください。

学派に縛られず使い分けるという現実的な選択

理想的なのは、基本は古典学派で進め、どうしても難しい部分だけをロンドン学派的に処理することです。例えば、ドメインロジックは古典学派でしっかり守り、インフラ層との境界はモックで切り離すといった構成です。

学派の定義を完璧に守ること自体には、あまり価値がありません。大切なのは、あなたのチームが「自信を持ってコードをリリースできるか」という1点に尽きます。私は、両方の学派の良いところ取りをすることを「ハイブリッド学派」と勝手に呼んで推奨しています(笑)。

単体テスト設計で初心者がつまずきやすいポイント

テストを書き始めたばかりの人が必ず直面するのが、「テストが書きにくい」という壁です。これは技術不足というよりも、テスト対象のコードそのものに問題がある場合がほとんどです。

「テストを書くために、コードをどう直せばいいのかわからない」。そんな悩みを持つ方は多いでしょう。しかし、その「書きにくさ」こそが、コードの不備を教えてくれる貴重なアラートなのです。テスト設計の基本を学ぶことは、より良いプロダクトコードを書くための修行でもあります。

「テストが書きにくいコード」が教えてくれること

テストが書きにくい最大の理由は、隠れた依存関係です。クラスの中で勝手に他のオブジェクトを new していたり、グローバル変数に依存していたりすると、テストは困難になります。

このようなコードは、密結合で変更に弱いことを示唆しています。テストを書きやすくするために「依存性の注入(DI)」を導入すると、自然とコードの柔軟性が高まります。私は、テストのおかげで設計のセンスが磨かれたと実感しており、今ではテストのない開発は考えられません。

学派以前に意識したい基本ルール

どの学派を選ぶにせよ、守るべき鉄則があります。それは「1つのテストで1つのことだけを検証する」ことと、「テストにロジックを入れない」ことです。

テストコードの中に if 文や for 文が出てきたら、それは黄色信号です。テスト自体にバグが混入する可能性が高まり、何を検証しているのかが不透明になります。シンプルで、誰が読んでも一目で実行結果が予想できるテスト。これこそが、数年後の自分を助けてくれる最高の資産となります。

単体テストの世界は奥が深く、今回紹介した2つの学派の対立は、今もなおエンジニアの間で議論されています。しかし、重要なのは手法そのものではなく、テストによって「変化を恐れない開発」を実現することです。

まずは、あなたのプロジェクトで、リファクタリングを邪魔している「壊れやすいテスト」がないか探してみてください。もしあれば、それはロンドン学派の手法を過剰に適用しているサインかもしれません。少しずつ古典学派のアプローチを取り入れ、テストの保守性を高めていきましょう。

  • この記事を書いた人
  • 最新記事

トム

・都内自社開発企業勤務/Javaバックエンドエンジニア
/Java歴10年以上 ・首都圏在住30代
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎 詳細なプロフィール

-IT全般