モック・代役・偽実装をどう使い分けるか?依存関係を置き換えるテスト設計を解説
テストで依存関係を置き換える話になると、現場では「とりあえずモックを使う」という理解で進んでしまうことが少なくありません。しかし、実際には依存先を置き換える方法にはいくつかの種類があり、何を確認したいのかによって向いている手段は変わります。ある場面では戻り値だけ固定できれば十分ですし、別の場面では呼び出し有無や引数を確認したいことがあります。また、もっと実際に近い流れを軽く確認したいなら、簡易的に動く代替実装のほうが扱いやすいこともあります。つまり、依存関係の置き換えは一つの技法で片づけるより、確認目的に応じて整理したほうがテスト設計として分かりやすくなります。名前だけで選ぶのではなく、見たいものから逆算して考える必要があります。
さらに、この使い分けを曖昧なまま進めると、テストが壊れやすくなったり、準備コードが長くなったり、読み手にとって意図が伝わりにくい構成になったりしやすくなります。戻り値を固定したいだけなのに細かな呼び出し順まで確認してしまう、逆に呼び出し確認が重要なのに簡易実装でぼんやり流してしまう、といったずれが起きると、テストは増えていても品質確認の精度は上がりにくくなります。この記事では、依存関係を置き換える方法をどう整理すると分かりやすいか、戻り値固定、呼び出し確認、偽実装の考え方をどう分けるべきか、そして実務で壊れにくいテストへどう寄せるべきかを順番に整理していきます。単に用語を並べるのではなく、実際の判断軸として使える形で見ていきます。
1. 依存関係を置き換える方法をどう整理するか
依存関係を置き換える方法を整理するときにまず重要なのは、道具の名前から入るのではなく、「このテストで何を固定し、何を観測したいのか」から考えることです。たとえば、認証結果が成功のときと失敗のときで分岐を見たいだけなら、認証依存は一定の戻り値を返してくれれば十分かもしれません。一方で、通知処理が送信されたかどうかを確認したいなら、戻り値より呼び出しそのものが重要になります。さらに、保存処理のように、実際に近い流れを軽く確認したいなら、簡単に動く代替実装のほうが分かりやすいこともあります。つまり、置き換え方法の違いは技術分類というより、観測したい対象の違いとして整理したほうが実務では使いやすいです。ここを整理できていると、必要以上に複雑なテストを書きにくくなります。
また、この整理ができると、過剰なモック化や不必要な確認を避けやすくなります。全部をモックで細かく確認しようとすると、テストは実装詳細へ寄りやすくなりますし、逆に全部を簡易実装で済ませると、呼び出し条件のような重要な観測点が抜けることがあります。そのため、最初に「このテストは戻り値中心か」「呼び出し中心か」「流れ全体を軽く見たいのか」を分けることが重要です。方法を先に決めるのではなく、観測したいものを先に決める。その順番を守るだけでも、テスト設計はかなり整理しやすくなります。
1.1 置き換えが必要になる場面
依存関係の置き換えが必要になるのは、外部依存をそのまま使うと、単体テストとして重すぎたり、不安定だったり、確認したい責務がぼやけたりする場面です。データベース、外部API、通知処理、時刻取得、環境情報、認証基盤などを本物のまま使うと、接続状態や環境差分にテストが引っ張られやすくなります。本来見たいのはロジックの分岐や値の組み立てや失敗時の扱いなのに、外部の状態まで一緒に見てしまうと、単体テストとしての目的が薄くなりやすいです。つまり、置き換えが必要になるのは「本物を使うことが今の確認目的に対して重すぎる」ときです。テスト対象そのものではない要素が、結果や準備や切り分けに大きく影響するなら、置き換えを考える価値があります。
さらに、失敗条件を意図的に作りたいときにも置き換えは役立ちます。APIタイムアウト、認証失敗、空の検索結果、通知送信エラーのような条件は、本物で毎回再現するのが難しいことがあります。そうした場面では、依存先の返り方や動きを制御できるようにすることで、確認したい分岐やエラーハンドリングを短く安定して表現しやすくなります。正常系だけなら本物で何とかなることもありますが、異常系まで含めて意図的に試したいなら、置き換えはかなり有効です。特に実務では、異常条件の作りやすさがテストの実用性を大きく左右します。
1.2 何を固定し、何を確認したいのかを分ける
依存関係の置き換えを考えるうえで特に重要なのは、「固定したいもの」と「確認したいもの」を混ぜないことです。たとえば、検索結果が0件のときの画面分岐を確認したいなら、検索依存へ空リストを返させれば十分であり、何回呼ばれたかやどの順番で呼ばれたかは重要でないかもしれません。逆に、通知送信の呼び出し条件を確認したいなら、戻り値はあまり関係なく、呼び出されたかどうかと引数が重要になります。つまり、同じ依存先でも、テストの目的によって固定したい条件と観測したい結果は変わります。ここを分けずに考えると、確認項目が増えすぎてテストがぼやけやすくなります。
この整理がないと、確認項目がどんどん増えやすくなります。戻り値も確認し、呼び出し有無も確認し、回数も順序も引数も全部見てしまうと、テストは読みにくく壊れやすくなります。しかも、少しの実装変更で不要に落ちるようなテストになりがちです。だからこそ、「このテストで守りたいものは何か」を先に決め、それに合う置き換え方法を選ぶことが大切です。固定すること自体が目的ではなく、見たいものだけを安定して見られるようにすることが目的だと考えると整理しやすくなります。
戻り値を固定したい場面、呼び出しを見たい場面、簡易実装で代替したい場面の比較
| 観点 | 戻り値を固定したい場面 | 呼び出しを見たい場面 | 簡易実装で代替したい場面 |
|---|---|---|---|
| 主な目的 | 分岐や結果処理を確認する | 副作用や呼び出し条件を確認する | 流れ全体を軽く確認する |
| 向いている依存 | 認証結果、検索結果、設定値、時刻 | 通知、保存、イベント送信、監査記録 | リポジトリ、メモリ上の保存先、簡易サービス |
| 確認対象 | 戻り値による振る舞い | 呼び出し有無、回数、引数 | 結果の整合性、軽い現実性 |
| 注意点 | 固定条件が多すぎると単調になりやすい | 実装詳細へ寄りすぎやすい | 本物との差を見落としやすい |
1.3 単体テストで求める観点とのつながり
単体テストで大切なのは、小さな責務を速く、安定して、意図が明確な形で確認できることです。この観点に照らすと、依存関係の置き換えは単なる便利機能ではなく、単体テストの性質そのものを守るための方法として位置づけられます。戻り値を固定するのは分岐確認を軽くするためであり、呼び出し確認は副作用条件を明確にするためであり、偽実装は本物より軽い形で流れを保つためです。つまり、置き換え方法の違いは、そのまま単体テストで何を優先するかの違いでもあります。どれも「依存を消す」ことが目的ではなく、単体テストの焦点を守ることが目的です。
このように考えると、どの方法を選ぶかも分かりやすくなります。小さな条件分岐を見たいなら戻り値固定、通知の有無を見たいなら呼び出し確認、簡単な保存フローを軽く見たいなら偽実装、というように、単体テストの観点と結びつけて判断できるからです。技法の名前で迷うより、単体テストとして何を守りたいかを先に考えるほうが実務では役立ちます。役割がはっきりしていると、テスト全体の粒度もそろえやすくなります。
1.4 外部依存を含む処理での考え方
外部依存を含む処理では、何をテスト対象の中心と見るかを先に決めることが重要です。たとえば、決済APIへ送るパラメータの組み立てが中心なのか、決済結果を受けた後の業務判断が中心なのか、送信失敗時のリトライ判断が中心なのかによって、適切な置き換え方法は変わります。同じ決済連携処理でも、どこに着目するかで見たいものは大きく違います。つまり、外部依存があるというだけで一律にモック化するのではなく、どこが中心責務かを見極めて置き換え方を選ぶべきです。
また、外部依存が重い処理ほど、単体テストではできるだけ確認対象を絞り、実接続の確認は結合テストへ寄せるほうが扱いやすくなります。単体テストで全部を見るのではなく、役割分担の中で依存の置き換えを位置づけることが大切です。外部依存を含む処理では、軽く見たいものと本物で見るべきものが混ざりやすいため、この切り分けを最初にしておくと設計もテストもかなり整理しやすくなります。
1.5 テストの目的から逆算して選ぶ
依存関係の置き換え方法は、道具の名前から選ぶより、テストの目的から逆算して選ぶほうが自然です。たとえば、「失敗したときに例外へ変換されるか」を見たいなら戻り値固定、「送信条件を満たしたときだけ通知されるか」を見たいなら呼び出し確認、「検索結果を一時的に保持して再利用する流れ」を軽く見たいなら簡易実装、といったように、確認したいものから手段を決めます。つまり、方法の選択は技術的流行ではなく、観測したい振る舞いから決めるべきです。この順序を守るだけで、余計な技法を使わずに済むことも多くなります。
この発想があると、不要な複雑さを避けやすくなります。何を見たいかが先に決まっていれば、必要以上にモックを設定したり、逆に粗すぎる代替でごまかしたりしにくくなるからです。テスト設計では、方法より目的を先に置くことが重要です。目的が明確なら、使う道具は自然と絞られますし、テストの意図も読み手へ伝わりやすくなります。
2. 戻り値を固定したいときの考え方
戻り値を固定する置き換えは、依存関係を置き換える方法の中でも最も分かりやすく、よく使われる形の一つです。テスト対象が依存先からどんな結果を受け取るかをこちらで決め、その結果に応じてどう振る舞うかを見るために使います。認証成功・失敗、検索結果あり・なし、設定値ON・OFF、現在時刻の固定、乱数の固定などが代表的です。つまり、戻り値固定の目的は、依存先そのものを確認することではなく、その返却結果を受けたテスト対象の判断を確認することにあります。依存先はあくまで条件を作るための手段として使われます。
また、戻り値を固定する方法は、テスト準備を短くしやすいという利点もあります。本物の依存先で条件を整えようとすると、DBへデータを入れたり、外部環境を調整したり、現在時刻を合わせたりする必要がありますが、戻り値を固定できればそうした準備をかなり省きやすくなります。ただし、便利だからといって何でも戻り値固定で済ませるのではなく、「今本当に見たいのが返却結果を受けた判断なのか」を意識することが大切です。返り値以外に意味があるなら、別の置き換え方のほうが自然な場合もあります。
2.1 条件分岐だけを確認したい場面
条件分岐だけを確認したい場面では、戻り値固定が非常に有効です。たとえば、ユーザーが存在するときは更新処理へ進み、存在しないときはエラーを返す、設定フラグが有効なら通知を準備し、無効なら何もしない、といった処理では、依存先が返す結果さえ固定できればロジックの確認に集中しやすくなります。分岐条件が明確に表現できるので、テストも短くなりやすく、読みやすさも保ちやすいです。つまり、分岐確認では依存先の本物らしさより、分岐条件を明確に作れることのほうが大切です。
このとき重要なのは、分岐に関係のない条件をできるだけ増やさないことです。戻り値を固定できるなら、それ以外の周辺条件はなるべくシンプルにしておいたほうが、何を確認したいテストなのかが伝わりやすくなります。条件分岐を見たいテストでは、依存先は「必要な条件を返してくれる装置」と割り切るくらいがちょうどよいことも多いです。周辺条件を盛り込みすぎると、分岐確認そのものが見えにくくなります。
2.2 特定の結果を安定して返したい場面
依存先の結果が毎回変わるとテストが不安定になる場面では、特定の結果を安定して返すこと自体に大きな価値があります。たとえば、現在時刻を返す依存、乱数を返す依存、外部設定値を返す依存などは、本物をそのまま使うと毎回微妙に結果が変わる可能性があります。こうした依存を固定すると、テストはかなり再現しやすくなります。つまり、戻り値固定は分岐確認だけでなく、揺れやすい依存を安定化するためにも使われます。特に時刻や乱数のような要素は、テスト不安定の原因になりやすいため、固定できる効果が大きいです。
また、安定した結果を返せるようになると、境界条件も確認しやすくなります。期限切れ直前の時刻、上限ちょうどの値、認証失敗コードなど、狙った条件を繰り返し作れるからです。たまたま今の環境で成立している条件ではなく、意図した条件をいつでも再現できることがテストの価値になります。そのため、再現性が重要な場面では戻り値固定の価値が高くなります。結果を固定できることは、単なる準備の簡略化以上に意味があります。
戻り値固定と相性の良い代表例
- 認証結果の固定
- 検索結果の固定
- 現在時刻の固定
- 乱数の固定
- 設定値や環境値の固定
- 外部サービス応答の成功・失敗切り替え
- 空配列やNULL相当の返却
- 特定エラーコードの返却
2.3 異常結果を意図的に返したい場面
戻り値固定の強みが特に出やすいのが、異常結果を意図的に返したい場面です。依存先が失敗を返したとき、空結果を返したとき、不正な形式の結果を返したときに、テスト対象がどう振る舞うかを確認したい場合、本物の依存で毎回その状態を作るのは難しいことがあります。外部APIのエラーや通信失敗を都合よく起こすのは現実的でないことも多いからです。つまり、戻り値固定は異常系の再現性を高めるためにも非常に有効です。異常を「待つ」のではなく、「作る」ことができるようになります。
ただし、異常結果を返すときも、何を確認したい異常なのかは明確にしておく必要があります。空結果なのか、例外相当の失敗なのか、認証拒否なのかで、テスト対象が取るべき行動は変わるからです。一つのテストで多くの異常を混ぜず、一つの失敗条件に対して一つの期待を置くほうが、意図は伝わりやすくなります。異常を再現しやすいからこそ、確認観点を絞ることがより重要になります。
2.4 前提条件を短く保ちたい場面
戻り値固定は、前提条件を短く保ちたい場面でも使いやすいです。たとえば、DBへ複数テーブル分のデータを入れなくても「検索結果が一件ある状態」を表現できたり、複雑な認証設定をしなくても「認証成功状態」を作れたりすると、テスト準備がかなり軽くなります。前提が短いと、テストの意図も読み取りやすく、保守もしやすくなります。つまり、前提条件を最小限へ絞りたいとき、戻り値固定は有効な方法です。テストの焦点をぼかさないためにも、前提は短いほうが扱いやすいです。
前提が短いテストは、読みやすく、失敗原因も追いやすくなります。これは単に書くのが楽というだけでなく、長く保守するうえでも大きな利点です。また、レビュー時にも「何を作っているか」が見えやすくなります。そのため、確認したいものが返却結果中心なら、前提条件をわざわざ重くするより、戻り値固定で短く表現したほうがよいことが多いです。準備の軽さはそのままテストの明快さにもつながります。
2.5 準備コードを増やしすぎない工夫
戻り値固定は便利ですが、設定が増えすぎると準備コードが長くなり、かえって読みにくくなることがあります。複数依存へ多数の値を設定し、さらに各ケースで細かな条件を変えていると、肝心の期待結果が埋もれやすくなります。戻り値を固定する方法は本来、テストを軽くするためのものなので、準備コードが主役になるのは避けたいところです。つまり、戻り値を固定するときも、「必要な返り方だけを明示する」ことが大切です。
工夫としては、よく使う成功結果や失敗結果を小さな部品としてまとめておく、ケースごとの差分だけを上書きする、関係のない依存まで同時に設定しないといった方法があります。共通パターンを少し整理するだけでも、テストはかなり見やすくなります。戻り値固定はテストを軽くするための方法なので、準備の重さで逆効果にならないようにする必要があります。シンプルさを失わない範囲で使うのが理想です。
3. 呼び出しを確認したいときの考え方
依存関係を置き換えるとき、戻り値よりも「呼び出されたかどうか」が重要になる場面があります。たとえば、条件を満たしたときだけ通知を送る、認可に失敗したら保存処理を呼ばない、状態が変わったときだけ監査ログを残す、といったケースです。このような場面では、依存先がどんな値を返したかより、そもそも呼ばれたのか、何回呼ばれたのか、どの値で呼ばれたのかが重要になります。つまり、呼び出し確認中心の置き換えは、副作用や外部アクションの条件を見たいときに向いています。ここでは返却値よりも「起きたこと」が確認対象になります。
ただし、呼び出し確認は便利な反面、実装詳細へ寄りやすい観点でもあります。どのメソッドを何回呼んだかは、利用者から見た振る舞いというより内部の手続きに近いため、必要以上に確認すると壊れやすいテストになりやすいです。そのため、呼び出し確認は「その呼び出し自体が意味を持つかどうか」を基準に使うのが重要です。確認できることが多いほど、確認しない勇気も必要になります。
3.1 呼び出し有無を確認したい場面
呼び出し有無の確認が有効なのは、依存先へのアクションそのものが結果として重要な場面です。たとえば、注文確定時にだけメール送信が走る、条件不一致なら保存しない、権限不足なら監査ログも通知も出さない、といったケースでは、呼び出されたかどうかがそのまま振る舞いの一部になります。ここで見たいのは、内部で処理されたかどうかではなく、「副作用が発生する条件を守れているか」です。つまり、呼び出し有無を確認するテストは、「その副作用が起きるべきか、起きないべきか」を見るためのものです。
このとき注意したいのは、呼び出されたこと自体に意味がある場合だけ確認することです。単に内部であるメソッドを使っているというだけなら、確認する価値が低いこともあります。たとえば、内部の補助処理が別関数へ切り出されたかどうかまで追う必要はない場面も多いです。外から見た振る舞いへ関わる副作用に絞ると、呼び出し有無の確認は分かりやすくなります。副作用がビジネス上の意味を持つときにこそ、この観点が活きます。
3.2 引数の内容を確認したい場面
呼び出し有無だけでは不十分で、依存先へ渡した値そのものが正しいかを確認したいこともあります。たとえば、メール件名や宛先、外部APIへ組み立てたリクエスト、DB保存時のパラメータ、イベント通知のペイロードなどです。こうした場面では、「呼ばれた」だけでなく、「何を渡したか」が重要になります。とくに境界で値を変換したり組み立てたりする処理では、引数確認に意味があります。つまり、引数確認は、依存境界で値をどう組み立てたかを見るための確認です。
ただし、引数確認も全部の項目を見る必要があるとは限りません。意味のある重要項目だけを見れば十分なことも多いです。細部まで固定すると実装変更に弱くなるため、「この値だけは間違ってはいけない」という観点に絞って確認するのが望ましいです。全文字列や全パラメータを固定しなくても、必要な保証は十分にできる場合があります。確認対象を絞ることで、テストの意図も伝わりやすくなります。
有無、回数、順序、引数の確認観点の整理
| 確認観点 | 何を見るか | 向いている場面 |
|---|---|---|
| 有無 | 呼ばれたかどうか | 通知、保存、副作用の発生条件 |
| 回数 | 何回呼ばれたか | 重複防止、リトライ制御 |
| 順序 | どの順で呼ばれたか | 手順に意味がある処理 |
| 引数 | 何を渡したか | パラメータ組み立て、通知内容確認 |
3.3 呼び出し回数を確認したい場面
呼び出し回数を確認したいのは、重複実行が問題になる場面です。たとえば、同じ通知を二回送ってはいけない、保存処理は一回だけでよい、リトライは最大三回までといったケースでは、回数そのものが品質に関わります。ここでは「呼ばれたかどうか」だけでは不十分で、「何回呼ばれたか」がそのまま期待結果になります。つまり、回数確認は「何回呼ばれても同じ」処理ではなく、「回数制御が意味を持つ」処理に対して有効です。
一方で、回数確認は実装変更で壊れやすいこともあります。たとえば内部で最適化して呼び出し回数が変わっただけで、外から見た結果は同じということもあります。そのため、本当に回数が仕様として意味を持つ場合だけに使うほうが健全です。回数が変わると業務上の問題になるのか、それとも内部都合にすぎないのかを見極める必要があります。意味のある回数だけを確認することが大切です。
3.4 呼び出し順序が重要になる場面
呼び出し順序の確認は、さらに慎重に使うべき観点です。順序に意味がある処理、たとえば最初に認可し、その後で更新し、最後に通知する、といった流れでないと不整合が起きる場合には確認価値があります。しかし、多くの場合、内部順序は実装の都合で変わり得るため、順序確認はかなり壊れやすいテストになりやすいです。順序を固定しすぎると、リファクタリングに弱いテストが増えやすくなります。つまり、順序確認は「変わると意味が壊れる場合」に限って使うべきです。
実務では、順序確認を安易に使うと少しのリファクタリングで大量のテストが落ちやすくなります。そのため、順序を見たい場合でも、本当に仕様上必要なのか、それとも内部手順を固定しているだけなのかを見極めることが大切です。順序が観測しやすいから確認する、ではなく、順序に業務上の意味があるから確認する、という基準が必要です。ここを間違えると、テストは厳密そうに見えて実際にはもろくなります。
3.5 確認しすぎで読みにくくなる問題
呼び出し確認系のテストで特に起きやすいのが、確認項目を増やしすぎて読みづらくなる問題です。有無、回数、順序、引数を一つのテストへ全部詰め込むと、何を守りたいのかが分からなくなります。しかも、少しの実装変更でどこかが壊れやすくなり、保守負担も上がります。確認できるものを全部見ると、かえって意図がぼやけることはよくあります。つまり、呼び出し確認は観測しやすいからこそ、確認する項目を絞る必要があります。
壊れにくいテストへ寄せたいなら、「このテストでは何を主に見ているか」を明確にし、その主目的に直接関係する確認だけを残すのが有効です。呼び出し確認は便利ですが、多ければ強いわけではありません。主目的が伝わる構成になっているかどうかのほうが大切です。必要ならテストを分けて、それぞれの確認観点を小さくしたほうが読みやすくなります。
4. 偽実装を使うときの考え方
偽実装は、単純な戻り値固定や厳密な呼び出し確認とは少し違い、簡易的に動く代替実装を置いてテストする考え方です。たとえば、メモリ上で保存できるリポジトリ、簡易的な検索サービス、テスト用の設定ストアなどがこれにあたります。本物のDBや外部サービスは使わないものの、一定の流れや状態変化は持たせたいときに向いています。つまり、偽実装は「完全な作り物」ではなく、「軽く動く代替」として考えると分かりやすいです。本物ほど重くないが、単なる固定戻り値よりは流れを持てる、という中間的な位置づけです。
この方法の利点は、戻り値固定だけでは見えにくい流れや状態変化を、比較的自然な形で確認しやすいことです。一方で、本物との差分が大きくなりすぎると、見ているつもりの連携や整合性が実際には見えていないこともあります。そのため、偽実装は便利ですが、どこまでを代替し、どこから先は本物で見るべきかを意識して使う必要があります。便利だから広げすぎると、テスト用の別実装を育てることになってしまうため、線引きが重要です。
4.1 簡易的に動く代替実装を置く場面
簡易的に動く代替実装が向いているのは、依存先の結果だけではなく、ある程度の状態変化や流れも含めて見たい場面です。たとえば、保存後に取得できること、複数件登録した結果が反映されること、更新によって状態が変わることなどを軽く確かめたいなら、単に戻り値を固定するよりも、メモリ上で動く簡易実装のほうが自然に表現しやすくなります。保存した結果が後続処理へどう影響するかを一連の流れとして見たいときに、偽実装は役立ちます。つまり、偽実装は「流れを保ちつつ重さを減らしたい」ケースで有効です。
また、こうした代替実装は、テストコードの可読性を高めることもあります。過剰なモック設定を書かなくても、実際に保存して取り出すという形で前提を作れるため、読む側にも意図が伝わりやすい場合があります。状態をまたぐ処理では、戻り値を一つずつ設定するより自然な流れで書けることがあります。とくに、複数ステップを含む処理ではこの利点が大きくなります。
4.2 実際に近い流れを軽く確認したい場面
偽実装の大きな利点は、本物ほど重くないのに、ある程度実際に近い流れを保てることです。たとえば、リポジトリへ保存したあと別の処理で読み直す、ある条件で状態を更新する、といったフローは、戻り値固定だけでは少し不自然になりやすいです。こうした流れを全部モックの返り値だけで表そうとすると、かえって前提設定が複雑になることもあります。そうした場面では、簡単な代替実装があると、処理の流れを比較的自然に保ったまま単体テストしやすくなります。
ただし、ここで見るのはあくまで「軽い流れ」であり、本物との契約整合性や性能特性まで確認するわけではありません。そのため、偽実装は結合テストの代わりではなく、単体テスト寄りの範囲で流れを見やすくするための手段として使うべきです。流れが自然に見えるからといって、本物に近いと誤解しないことが大切です。あくまで確認したい範囲だけを軽く再現するためのものです。
実依存、簡易代替、完全な呼び出し確認の違い
| 観点 | 実依存 | 簡易代替 | 完全な呼び出し確認 |
|---|---|---|---|
| 現実性 | 高い | 中程度 | 低い |
| 速度 | 重くなりやすい | 比較的軽い | 軽い |
| 状態変化の確認 | しやすい | しやすい | しにくい |
| 呼び出し詳細の確認 | しにくい | 場合による | しやすい |
| 単体テストとの相性 | 場合による | 高いことが多い | 高いことが多い |
4.3 メモリ上で完結させたい場面
偽実装が特に扱いやすいのは、メモリ上で完結させられる場面です。DBの代わりにメモリ上のリストや辞書で保持する、検索結果を簡易条件で返す、設定値をテスト内で持つといった形にすると、状態を扱いながらも外部環境へ依存しにくくなります。外部接続なしで状態変化を表現できるため、実行も速く、前提も把握しやすくなります。つまり、メモリ上で完結する代替は、軽さと状態管理のバランスが取りやすいです。
実務では、この手法によって単純な永続化フローや取得フローをかなり見やすくできます。ただし、メモリ上で動くからといって本物の制約やクエリ挙動まで再現できるわけではないため、その範囲は意識しておく必要があります。たとえば一意制約やトランザクション動作は、本物と同じにはならないかもしれません。その違いを理解したうえで使うことが大切です。便利さに引っ張られて過信しないことが重要です。
4.4 実行速度と現実性のバランスを取る場面
偽実装は、実行速度と現実性のちょうど中間を取りたいときに向いています。戻り値固定だけでは流れが見えにくいが、本物を使うと重い、という場面は実務でよくあります。たとえば、複数回の保存と取得をまたぐロジックや、軽い集約処理を含むケースでは、簡易代替がちょうどよいことがあります。つまり、偽実装は「軽いけれど無機質すぎない」テストを作りたいときに役立ちます。単純なモックでは表しにくい自然な流れを、重すぎずに持ち込めるのが強みです。
この使い方がうまくはまると、テストは読みやすく、速く、しかも流れをある程度保ったものになります。ただし、そのバランスは依存先の性質によって変わるため、いつも偽実装が最適というわけではありません。流れを見たいのか、条件だけを見たいのかで向き不向きは変わります。だからこそ、偽実装は便利な中間手段として使いどころを見極める必要があります。
4.5 複雑にしすぎないための線引き
偽実装を使うときに大切なのは、本物に寄せすぎて複雑にしないことです。簡易的な代替で十分な場面なのに、制約再現、検索最適化、競合制御まで持ち込み始めると、それはもはや小さなテスト用実装ではなく、別の本格実装になってしまいます。そうなると、保守対象が増え、テストのための実装が逆に重くなります。つまり、偽実装は便利ですが、「必要な性質だけを残す」という線引きが重要です。便利だからといって本物に近づけすぎると、得られるはずだった軽さを失いやすくなります。
どこまでを再現するかは、何を確認したいかで決めるべきです。流れと状態変化だけ見たいなら、その範囲で止める。契約整合性まで見たいなら結合テストへ回す。こうした線引きがあると、偽実装は扱いやすくなります。テスト用実装を育てること自体が目的ではないため、確認したい性質だけを小さく持たせる意識が大切です。シンプルさを保てる範囲で使うのが理想です。
5. どの置き換え方法を選ぶべきか
依存関係を置き換える方法を選ぶときは、道具の名前より確認したいものを基準に考えるべきです。戻り値中心の確認なのか、呼び出し中心なのか、軽い流れ確認なのかによって、向いている方法は変わります。つまり、モック、代役、偽実装といった名前で選ぶのではなく、テストの焦点から逆算して選ぶことが重要です。同じ依存でも、見たいものが変われば選ぶべき置き換え方も変わります。この視点があると、道具選びがぐっとシンプルになります。
また、選択の際には、壊れやすさ、準備量、可読性、保守性も見ておく必要があります。一見厳密に見える方法が、長期的には一番扱いにくいこともあるからです。テストは通すことだけでなく、読み、直し、維持することまで含めて評価すべきです。書いた瞬間の便利さだけでなく、半年後に読んでも意味が分かるかどうかも大切な判断軸になります。
5.1 確認したいものが戻り値中心か
もし確認したいのが、依存先の結果を受けた分岐や処理結果であるなら、戻り値固定がもっとも自然です。認証成功か失敗か、検索結果があるかないか、時刻が期限内かどうかといった条件は、返り値を明示できればかなり短くテストできます。返り値さえ作れればよいので、準備もシンプルになりやすいです。つまり、戻り値中心の確認なら、余計な呼び出し確認や複雑な代替実装は不要なことが多いです。
この判断ができると、テスト準備もかなり軽くなります。返り方だけ作ればよいので、周辺環境や複数依存の設定を増やしすぎずに済みます。また、テストの主眼も読み手に伝わりやすくなります。そのため、分岐確認や例外変換のような場面では、まず戻り値中心で考えるのが分かりやすいです。最初にシンプルな方法で足りるかを考えることが重要です。
5.2 確認したいものが呼び出し中心か
一方で、依存先へのアクションそのものが重要なら、呼び出し確認が中心になります。通知を送るか、保存するか、監査ログを書くか、イベントを発行するかといった副作用系の処理では、結果より「起きたこと」を観測したいからです。ここでは、返り値の内容より、副作用が発生した条件のほうが重要になります。つまり、呼び出し中心の確認では、戻り値固定よりも有無・回数・引数の確認に価値があります。
ただし、この場合も何をどこまで確認するかは絞る必要があります。呼び出し中心だからといって順序も全引数も全部見る必要があるとは限りません。本当に守りたい副作用条件だけを見る意識が重要です。副作用に意味があるなら呼び出し確認は有効ですが、確認範囲を広げすぎると実装詳細へ寄りやすくなります。必要な分だけを観測するのが理想です。
置き換え方法を選ぶときの判断軸
- テストの目的は何か
- 壊れやすい確認になっていないか
- 準備コードが過剰になっていないか
- 読み手に意図が伝わるか
- 将来の保守に耐えやすいか
5.3 実際に近い流れを見たいか
戻り値でも呼び出しでもなく、軽く実際に近い流れを見たいなら、偽実装や簡易代替のほうが向いています。保存して読み直す、状態が蓄積される、複数操作をまたいで結果が変わるといった場面では、一定の状態を持つ代替が分かりやすいことがあります。単純な固定値の返却だけでは、流れの自然さが失われやすいからです。つまり、確認したいものが「一点の返却値」ではなく「小さな流れ」なら、簡易実装を選ぶ余地があります。
ただし、実際に近い流れを見たいからといって、本物との差をすべて埋めようとしないことが大切です。単体テストで見たい範囲に必要な現実性だけを残すのが、壊れにくい構成につながります。流れを見たいのか、契約まで見たいのかを分けて考える必要があります。流れの自然さを求めるときこそ、どこで止めるかの判断が重要になります。
5.4 テストが壊れやすくならないか
どの方法を選ぶにしても、テストが壊れやすくならないかは大事な判断軸です。呼び出し確認が多すぎないか、偽実装が複雑すぎないか、戻り値固定が細かすぎないかを見ておくと、後からの保守がかなり違ってきます。一見しっかり確認しているように見えても、少しの実装変更で無意味に落ちるなら、長期運用では扱いにくくなります。つまり、今書きやすいかだけでなく、少しの実装変更で無意味に落ちないかを考える必要があります。
壊れにくさを優先するなら、まず振る舞い中心で見て、必要なときだけ詳細な観測を足すほうが安全です。観測できるから全部見る、ではなく、本当に守りたいものだけを残す判断が大切です。確認観点が少ないほうが常によいわけではありませんが、意味のある確認だけが残っている状態のほうが健全です。壊れにくさは、確認の少なさではなく、確認の適切さで決まります。
5.5 チームで判断基準を揃えられるか
置き換え方法の選択は、個人の好みだけに任せるとテスト全体のばらつきが大きくなりやすいです。ある人は何でも呼び出し確認を入れ、別の人は全部偽実装で流すといった状態では、読み方も保守方針も揃いにくくなります。結果として、レビューもしづらくなり、チームとしてのテスト方針が見えにくくなります。つまり、どの方法を選ぶかはチームである程度共通の判断軸を持っておくほうが安定します。
この基準は厳密なルールでなくても構いません。「分岐確認は戻り値中心」「副作用は有無中心」「実流れは簡易代替も可」といった方向性が共有されているだけでも、テストの統一感はかなり上がります。また、レビュー時にも「なぜこの置き換え方なのか」を会話しやすくなります。共通言語があるだけで、テスト設計はかなり安定しやすくなります。
6. 壊れにくいテストへ寄せる工夫
依存関係を置き換えるテストは、便利な反面、少し油断すると実装詳細へ寄りすぎたり、準備が長くなりすぎたりして壊れやすくなります。そのため、壊れにくいテストへ寄せるには、何を確認するかを絞り、振る舞い中心に見て、準備を短くし、読み手に意図が伝わる形を意識することが重要です。置き換えの方法を知っていることと、それを小さく使えることは別ですが、後者のほうが長期運用では効いてきます。つまり、置き換え方法を知ることと同じくらい、それをどう小さく使うかが大切です。
また、壊れにくさは書き方だけでなく、テストの構成にも関係します。一つのテストで多くを見ない、目的ごとに分ける、名前から意図が伝わるようにするといった基本的な工夫が、結果的に長く使えるテストにつながります。特別なテクニックよりも、基本的な整理の積み重ねが効く場面です。複雑な依存置き換えほど、構成の素直さが価値を持ちます。
6.1 実装詳細ではなく振る舞いを見る
壊れにくいテストへ寄せる最も基本的な考え方は、実装詳細ではなく振る舞いを見ることです。利用者や呼び出し元から見て重要な結果、副作用、状態変化を中心に確認し、内部の細かな手順は必要なときだけ見るようにすると、少しのリファクタリングで無駄に壊れにくくなります。外から見た意味が同じなら、内部の組み立て直しでテストが落ちる必要はないことも多いです。つまり、置き換えを使うときほど「外から見て意味があるか」を意識する必要があります。
この観点が弱いと、呼び出し順や内部メソッド利用まで細かく固定してしまいがちです。そうすると、挙動は変わっていないのにテストだけが壊れる、という状況になりやすくなります。振る舞い中心へ戻る意識があると、確認項目も自然に絞りやすくなります。テストが守るべきものを間違えないことが、壊れにくさの出発点になります。
6.2 確認項目を増やしすぎない
一つのテストへ確認項目を詰め込みすぎると、どれか一つの理由で落ちたときに意図が読みにくくなります。戻り値確認、呼び出し有無、回数、引数、順序を全部一度に見るような構成は、情報量が多く見えても読み手には負担が大きいです。しかも、実装を少し変えただけで複数箇所が連鎖的に落ちやすくなります。つまり、確認項目は多いほど強いのではなく、目的に対して過不足がないことが重要です。
テストが壊れたときに何を直せばよいか分かりやすい状態を目指すなら、一つのテストで見るものは絞ったほうがよいです。多くを確認したいなら、テスト自体を分けたほうが意図も明確になります。また、分けておくと失敗の意味も読みやすくなります。確認項目を減らすことは弱さではなく、意図を強くするための整理だと考えると分かりやすいです。
壊れにくいテストへ寄せるための基本観点
- 1テスト1目的を意識する
- 過剰な確認を避ける
- 準備コードを短く保つ
- テスト名で意図を示す
- 期待値を明確にする
6.3 前提条件を短くする
依存関係を置き換えるテストでは、前提条件を短く保つことが重要です。複数の依存へ大量の設定を入れ、状態や引数や順序を全部整えないと動かないテストは、それだけで読みにくく壊れやすくなります。前提が長いほど、「何が本当に必要で、何がたまたま混ざっているだけか」が分かりにくくなるからです。つまり、テストの前提が長いということは、確認対象が広がりすぎているサインでもあります。
前提を短くするには、確認したい観点に関係のない依存設定を減らし、共通化できる成功パターンや失敗パターンを整理し、必要な差分だけをケースごとに書くといった工夫が有効です。たとえば、毎回同じ成功状態を一から書くのではなく、小さな部品として持っておくと読みやすくなります。前提条件の短さは、そのままテストの理解しやすさへつながります。短い前提は、保守しやすいテストの土台にもなります。
6.4 テスト名で意図を伝える
依存関係を置き換えたテストは、準備コードだけを見ると何を確認したいのか読み取りにくいことがあります。そのため、テスト名で意図を補うことがとても重要です。たとえば、「認証失敗時に通知せずエラーを返す」「検索結果が空のとき空一覧を返す」といった形で、条件と期待結果が読める名前にしておくと、準備の意味も理解しやすくなります。名前だけで大まかな意図が伝わると、読む側の負担はかなり下がります。つまり、テスト名はコメント以上に重要な説明装置です。
とくに、呼び出し確認や偽実装を使うテストでは、名前が弱いと何を見ているのか分かりにくくなります。名前だけで意図の骨格が伝わる状態を目指すことが大切です。また、テスト名が意図を示していれば、過剰な確認を減らす助けにもなります。名前と実際の確認内容が一致しているかを見直すだけでも、構成の無駄に気づきやすくなります。
6.5 読み手が理解しやすい構成にする
壊れにくいテストは、書き手だけでなく読み手にとって理解しやすい構成になっています。準備、実行、確認の流れが自然で、どの依存をどう置き換えているのかが分かりやすく、期待結果が埋もれていない状態です。依存置き換えの種類が増えるほど、構成の整理が重要になります。つまり、依存関係の置き換えをうまく使うには、テクニック以上に構成力が重要です。
読みやすい構成にしておくと、レビューもしやすく、将来の修正も楽になります。テストは一度書いて終わりではないため、読み手を意識した構造へ寄せることが結果的に壊れにくさへつながります。読みやすいテストは、チーム全体で運用しやすいテストでもあります。構成の分かりやすさは、そのまま継続性につながります。
7. 単体テストと結合テストでの役割分担
依存関係を置き換える設計を考えるとき、単体テストと結合テストの役割分担を明確にしておくことが大切です。単体テストでは依存を置き換えて、ロジックや分岐や例外処理を軽く安定して確認し、結合テストでは実際の依存とつないで契約整合性や連携結果を確認する、という整理が分かりやすいです。この分担がないと、単体テストが重くなりすぎたり、逆に結合でしか見えない問題を単体に期待しすぎたりします。つまり、依存の置き換えは「全部を偽物で済ませる」ためのものではなく、見るべき場所を分けるためのものだと言えます。
この役割分担が曖昧だと、単体テストへ実依存を入れすぎて重くしたり、逆に結合テストまで置き換えだらけにして実連携不具合を見逃したりしやすくなります。そのため、どこで置き換え、どこで本物を見るかをチームで共有しておくことが重要です。役割が見えていると、単体テストも結合テストも過不足なく設計しやすくなります。置き換え方法そのものより、この分担の理解のほうが重要なことも多いです。
7.1 単体テストで置き換えるべき依存
単体テストで置き換えるべきなのは、重い、遅い、再現しづらい、副作用がある依存や、今見たい責務に対してノイズになりやすい依存です。たとえば、DBアクセス、外部API、時刻取得、乱数、通知、認証結果などは、単体テストでは置き換えたほうがロジック確認に集中しやすくなります。これらを本物で持ち込むと、単体テストの焦点が広がりすぎてしまうからです。つまり、単体テストでは「対象の判断」を見るために依存を制御するという発想が基本になります。
この整理ができると、単体テストは速く、安定し、変更にも追従しやすくなります。とくに変更頻度の高い箇所や、細かい条件分岐が多い箇所では、置き換えの価値が大きくなります。また、失敗したときの原因も対象ロジックへ寄せやすくなります。単体テストでは、依存先の正しさより「依存結果を受けてどう振る舞うか」を見ることが大切です。
7.2 結合テストで実際につなぐべき依存
一方で、実際につなぐべき依存は結合テストで確認する必要があります。DBスキーマとの整合、API契約の一致、実際の通知フォーマット、認証基盤との連携条件などは、置き換えたテストでは見えません。戻り値や呼び出し条件をきれいに整えていても、本物との接続で初めて露出する問題は確実にあります。つまり、結合テストでは「本物とつないだときに本当に成立するか」を見ることが役割になります。
この役割を結合テストへ残しておくことで、単体テストではあえて見ない部分を安心して切り分けやすくなります。単体テストと結合テストはどちらが上という関係ではなく、見る対象が違う補完関係にあります。単体で軽く見て、結合で現実の接続を見る。この分担が明確だと、依存の置き換えも過不足なく使いやすくなります。
単体テスト・結合テストで見る範囲の比較
| 観点 | 単体テスト | 結合テスト |
|---|---|---|
| 主な対象 | ロジック、分岐、例外処理、条件判定 | 契約整合性、接続後の挙動、設定差異 |
| 依存関係 | 置き換えることが多い | 本物を使うことが多い |
| 速度 | 軽さ重視 | やや重くてもよい |
| 再現性 | 高く保ちやすい | 環境影響を受けやすい |
| 確認目的 | 小さな責務を絞って見る | 実連携の成立を確認する |
7.3 置き換えすぎによる見落としを防ぐ
依存を置き換えること自体は有効ですが、置き換えすぎると本物との接続でしか見えない問題を見逃しやすくなります。たとえば、レスポンス形式の差、シリアライズ条件の違い、設定不足、タイムゾーン差などは、単体テストでは通っても結合で崩れることがあります。つまり、置き換えは単体テストを強くしますが、それだけで十分とは言えません。単体で見えるものと、本物でしか見えないものを意識して分けておく必要があります。
この見落としを防ぐには、単体と結合の役割分担をはっきりさせることが重要です。単体でロジックを押さえ、結合で実契約を見る、という流れを意識すると、置き換えのメリットを活かしつつ弱点も補いやすくなります。テスト戦略全体の中で依存置き換えを位置づけることが大切です。置き換えそのものは目的ではなく、確認を分担するための手段です。
7.4 実務での使い分けを定着させる
実務で使い分けを定着させるには、個人の感覚に任せず、レビューやテスト方針の中である程度共通化することが大切です。どの依存は単体で置き換えるのか、どの確認は結合へ回すのか、呼び出し確認はどこまで許容するのかといった基準が揃っていると、テスト全体の統一感が出やすくなります。チーム内で方針が揃っていないと、読み方も書き方もばらつきやすくなります。つまり、置き換え方法の理解だけでなく、チーム運用としての基準づくりが必要です。
基準があると、新しく入ったメンバーも迷いにくくなり、テストコードの読み方も揃えやすくなります。さらに、レビューでも「この場面でなぜこの方法を選んだのか」を説明しやすくなります。テストは個人作業で書かれても、運用はチームで行うものだからです。共通の判断軸があるだけで、テスト設計の品質はかなり安定しやすくなります。
7.5 設計改善と合わせて見直す
依存関係の置き換えを考えるときは、テストだけでなく設計そのものも見直す価値があります。差し替えにくいなら依存の受け取り方を変える、偽実装が複雑すぎるなら責務分離を見直す、呼び出し確認が多すぎるなら境界設計を整理する、といった改善へつなげやすいからです。テストしづらさは、そのまま設計の分かりにくさや責務混在のサインであることも多いです。つまり、置き換え方法の選択はその場のテスト都合だけでなく、コード構造の改善ヒントにもなります。
この視点があると、テストのしづらさを単なる作業負荷で終わらせず、設計改善の入口として使えるようになります。実務では、この視点の差が保守性の差になりやすいです。テストのために少し設計を整えることが、後の変更しやすさにも効いてきます。依存関係の置き換えは、テスト技法であると同時に、設計を見直すきっかけでもあります。
おわりに
依存関係を置き換えるテスト設計では、モック、代役、偽実装といった名前の違いを覚えること以上に、「何を固定し、何を確認したいのか」を整理することが重要です。戻り値を安定して返したいなら戻り値固定、呼び出しそのものに意味があるなら呼び出し確認、軽い流れや状態変化を見たいなら簡易的な代替実装、というように、テストの目的から逆算して選ぶと分かりやすくなります。こうした整理ができると、過剰な確認や複雑すぎる準備を避けやすくなり、読みやすく壊れにくいテストへ寄せやすくなります。方法そのものではなく、見たいものから選ぶことが一番大切です。
また、依存関係の置き換えは単体テストだけで完結する話ではなく、結合テストとの役割分担や、差し替えやすい設計とのつながりまで含めて考える必要があります。単体テストではロジックや条件分岐へ集中し、結合テストでは実際の依存とつないで契約整合性を確認する、という整理ができると、テスト戦略全体も見通しやすくなります。大切なのは、どの方法を使うかそのものではなく、確認したい品質に対して最も無理のない置き換え方を選ぶことです。そこまで整理できると、テストは単なる確認作業ではなく、設計と品質を支える仕組みとして機能しやすくなります。
EN
JP
KR