どこまでをユニットテストで検証し、どこからを結合テストで検証するべきなのか - 境界の引き方と実務の判断表

· · テスト, ユニットテスト, 結合テスト, テスト設計, Windows開発, C# / .NET

テスト設計の話で、毎回地味に難しいのが、どこまでをユニットテストに押し込み、どこからを結合テストへ上げるかです。

ここで危ないのは、

  • 速く回したいから、何でもユニットテストにする
  • 本物に近いから、何でも結合テストにする

という両極端です。

前者はモックだらけになって本番で壊れるポイントを見落としやすく、後者は遅くて壊れやすいテスト群になりがちです。
実務では、見るべき軸はもう少しはっきりしています。

  • 確かめたいのは、自分たちのロジックか、外とのつなぎ込みか
  • in-memory の fake で置き換えても、意味が落ちないか
  • DB / ファイル / HTTP / DI / 設定 / フレームワーク / OS の挙動が本題か
  • 大量の入力パターンを高速に回したいか

この 4 つが見えると、ユニットテストと結合テストの境界はかなり引きやすくなります。

この記事では、2026 年 3 月時点で参照できる Microsoft Learn と Martin Fowler の公開情報を前提に、ユニットテストと結合テストの境界を実務寄りに整理します。123

目次

  1. 1. まず結論
  2. 2. この記事でいうユニットテストと結合テスト
  3. 3. 一枚で見る判断表
  4. 4. ユニットテストで持つべきもの
    • 4.1. ユニットテストで mock が増えすぎるとき
  5. 5. 結合テストへ上げる 4 つの境界
    • 5.1. フォーマットの境界
    • 5.2. 配線の境界
    • 5.3. 環境の境界
    • 5.4. 時間の境界
  6. 6. よくある判断ミス
    • 6.1. Repository を mock して満足してしまう
    • 6.2. Controller / Endpoint のユニットテストでフレームワークまで見ようとする
    • 6.3. 結合テストで入力パターンを総当たりする
    • 6.4. CI から外部サービスの本番系をそのまま叩く
  7. 7. 実務でのおすすめ構成
  8. 8. 迷ったときに最後に見る 5 問
  9. 9. まとめ
  10. 10. 関連記事
  11. 11. 参考資料

1. まず結論

かなり雑に、でも実務で使いやすい言い方をすると、こうです。

  1. 純粋ロジックはユニットテスト
  2. 接続・配線・変換・環境差は結合テスト
  3. どちらでも検証できるなら、まずユニットテスト
  4. 結合テストは広く重くするより、境界を狭く絞る

ひとことで言えば、ユニットテストは「判断のテスト」、結合テストは「接続のテスト」です。

金額計算、状態遷移、入力検証、承認条件、例外の分類のように、外部資源なしで意味が完結するものは、ユニットテストへ寄せたほうが速く、壊れにくく、入力パターンも厚く回せます。
一方で、SQL の実行、JSON / CSV の直列化、ルーティング、モデルバインディング、DI 登録、ファイルロック、権限、COM 登録、32bit / 64bit、STA / MTA のような「つながった瞬間に裏切るもの」は、結合テスト側へ置いたほうが安全です。

Microsoft Learn の Integration tests in ASP.NET Core でも、統合テストは重要なインフラシナリオに絞り、ユニットテストで済むならそちらを選ぶよう整理されています。

2. この記事でいうユニットテストと結合テスト

ここでは、次のように整理します。

レベル 何を確かめるか 典型的な構成
ユニットテスト 分離された 1 責務の正しさ fake / mock / stub を使い、外部資源を切る
結合テスト 複数コンポーネントの接続と、インフラやフレームワークを含む挙動 実 DB、実ファイル、実 serializer、実 host、実 pipeline など
E2E / 機能テスト アプリ全体のユーザーフロー デプロイ済みアプリ、複数サービス、実ブラウザや実プロセス

.NET のユニットテスト整理では、よいユニットテストは fast / isolated / repeatable で、ファイルシステムや DB のような外部要因へ依存しないものとして説明されています。詳しくは Unit testing best practices for .NET が分かりやすいです。

また、結合テストは「別プロセスや別サーバーを必ず使う重いテスト」だけを指すわけではありません。
同一プロセス内でも、複数の実コンポーネントをつなぎ、フレームワークやインフラの本物の挙動を確かめるなら、それは結合テスト寄りです。

たとえば ASP.NET Core の controller action をユニットテストするとき、対象は action 本体の判断に絞り、routingmodel bindingfilters のようなフレームワーク側の相互作用は結合テストで扱う、という切り分けが公式にも示されています。詳しくは Unit test controller logic in ASP.NET Core を見ると整理しやすいです。

3. 一枚で見る判断表

まずは、いちばん実務で使いやすい表を置きます。

確かめたいもの 主力にするテスト 補足
金額計算、割引、状態遷移、入力検証 ユニットテスト 入力パターンを厚く回したい
例外の分類、エラーメッセージ選択、リトライするかの判断 ユニットテスト 実 I/O なしで意味が完結する
Repository の SQL / ORM 変換、transaction 結合テスト 実 DB や実 provider の挙動が本題
JSON / XML / CSV の serialize / deserialize 結合テスト wire format のズレは fake では見つけにくい
ルーティング、モデルバインディング、フィルター、middleware 結合テスト フレームワークとの接続確認
WPF / WinForms の ViewModel や Presenter の状態遷移 ユニットテスト UI を立てなくても意味がある
実際の Binding、Dispatcher、control lifecycle、message loop 結合テスト or UI テスト フレームワークとスレッドの挙動が主題
ファイルパス、権限、ロック、共有フォルダ、改行コード、文字コード 結合テスト OS とファイルシステムの実挙動が必要
COM 登録、32bit / 64bit、STA / MTA、DLL ロード 結合テスト 環境差とプロセス境界が主題
アプリ全体の起動、主要ユースケースの通し確認 E2E / スモーク 本数は少なくてよい

見方のコツは、どのテストが「本番で壊れる理由」にいちばん近いかです。
コードの置き場所ではなく、減らしたい不確実性で決めるほうがぶれません。

4. ユニットテストで持つべきもの

ユニットテストに向くのは、外界を取り去っても意味が残る責務です。

たとえば次のようなものです。

  • 業務ルール
  • 分岐
  • 状態遷移
  • 入力検証
  • エラー分類
  • リトライ方針の決定
  • ViewModel / Presenter の状態変化
  • 変換ロジックそのもの

特に、組み合わせが多いものほどユニットテストへ寄せる価値が高いです。

たとえば、

  • クーポンあり / なし
  • 在庫あり / なし
  • 初回注文 / 再注文
  • 管理者 / 一般ユーザー
  • 正常値 / 境界値 / 不正値

のように、分岐条件が増えるほど、結合テストで全部回すのは重くなります。
ここはユニットテストで細かく刻むほうが合理的です。

また、ユニットテストでは 外部要因を制御可能にしておく のが重要です。

  • 現在時刻は注入する
  • GUID や乱数は差し替えられるようにする
  • sleep で待たない
  • 実 DB や実ファイルに触れない
  • 実ネットワークに出ない

このへんが守られると、テストはかなり安定します。

4.1. ユニットテストで mock が増えすぎるとき

ユニットテストを書こうとしたら、

  • mock が 7 個いる
  • setup が長い
  • arrange が本体より長い
  • 何を確認したいのか見えない

となるなら、たいてい次のどちらかです。

  1. そのクラスが責務過多
  2. 本当は結合テストで確かめるべき配線を、ユニットテストへ押し込んでいる

mock は外界を切るための道具であって、本物との接続が正しいことを証明する道具ではありません
ここを取り違えると、「全部 green なのに本番で落ちる」が起きやすくなります。

5. 結合テストへ上げる 4 つの境界

結合テストへ上げるべき場所は、だいたい フォーマット、配線、環境、時間 の 4 つに整理できます。

5.1. フォーマットの境界

ここでいうフォーマットは、次のようなものです。

  • JSON / XML / CSV
  • DB の schema と mapping
  • nullable / precision / timezone
  • enum や日付のシリアライズ
  • 文字コードや BOM
  • 改行コード

Martin Fowler も、serialize / deserialize が入る境界は結合テスト候補として挙げています。詳しくは The Practical Test Pyramid が参考になります。

たとえば、

  • DTO を JSON にしたらフィールド名が違った
  • CSV の引用符や改行が壊れた
  • decimal が丸められた
  • DB で DateTimeOffset の扱いがずれた
  • null と空文字の扱いが想定と違った

のような不具合は、ユニットテストだけでは抜けやすいです。

5.2. 配線の境界

配線の境界は、たとえば次です。

  • DI 登録
  • 設定の bind
  • ルーティング
  • モデルバインディング
  • フィルター
  • middleware
  • host の起動
  • イベント配線
  • WPF の Binding や command 接続

ここは「自分の関数が正しいか」ではなく、複数の実部品が正しくつながっているか が本題です。

ASP.NET Core では、controller action のユニットテストは action の判断へ絞り、routingmodel bindingfilters は結合テスト側で見る整理が公式にあります。
Web でなくても考え方は同じで、デスクトップアプリでも、ViewModel の状態遷移はユニットテスト、実際の XAML Binding や Dispatcher を含む挙動は結合テスト寄りです。

5.3. 環境の境界

Windows 開発では、ここがかなり重要です。

  • ファイル権限
  • 共有フォルダ
  • ファイルロック
  • 一時ファイルからの rename
  • 管理者権限
  • サービス起動権限
  • COM 登録
  • 32bit / 64bit
  • STA / MTA
  • DLL のロード元

このへんは、OS や実行環境の条件そのもの が主役です。
in-memory fake では意味がかなり落ちるので、結合テストで押さえたほうが安全です。

特に、既存 Windows ソフトや COM / ActiveX を含む構成では、ロジックより先に 登録、bitness、スレッドモデル、権限 で転ぶことが普通にあります。
こういう失敗は、ユニットテストではなく、環境を含めた結合テストが拾う領域です。

5.4. 時間の境界

もうひとつ見落としやすいのが、時間と並行性です。

  • timeout
  • cancellation
  • retry の実挙動
  • timer 駆動
  • バックグラウンド処理の停止
  • race condition
  • shutdown 時の終了順序

ここで大事なのは、判断と実挙動を分ける ことです。

たとえば、

  • 何回まで retry するか
  • どの例外を retry 対象にするか

はユニットテストで十分です。
一方で、

  • 実際に timeout が効くか
  • cancellation が伝播するか
  • timer と非同期処理がぶつかったときに壊れないか
  • 終了時にハンドルやタスクがきれいに閉じるか

は結合テスト寄りです。

6. よくある判断ミス

6.1. Repository を mock して満足してしまう

Repository まわりを全部 mock で通しても、

  • SQL が正しいか
  • transaction が効くか
  • schema と一致しているか
  • mapping がずれないか
  • 文字コードや precision が壊れないか

は分かりません。

Repository は、ロジックのテスト対象というより、境界の接続点であることが多いです。
その場合はユニットテストより、結合テストの比重を上げたほうが実態に合います。

6.2. Controller / Endpoint のユニットテストでフレームワークまで見ようとする

controller action のユニットテストで見たいのは、

  • 条件分岐
  • 戻り値の選択
  • 依存サービスの呼び分け

あたりです。

一方で、

  • route が当たるか
  • model binding が通るか
  • filter が効くか
  • middleware を通した結果どう見えるか

は結合テスト側です。
ここを混ぜると、何が壊れたのか分かりにくくなります。

6.3. 結合テストで入力パターンを総当たりする

結合テストは本物に近いぶん、どうしても遅くなります。
だから、分岐の総当たりはユニットテスト、境界の代表ケースは結合テスト と分けたほうが得です。

Microsoft Learn の統合テスト解説でも、DB やファイルシステムに対しては全パターンを結合テストで回すのではなく、read / write / update / delete のような代表的なシナリオへ絞る 方向が勧められています。

6.4. CI から外部サービスの本番系をそのまま叩く

これは避けたほうが安全です。

結合テストは「本物らしさ」が大事ですが、だからといって毎回本番 SaaS や本番 API を叩く必要はありません。
Fowler も、外部サービスはローカルに立てる、fake を置く、もしくは専用の test instance を使う方向を勧めています。

実務では、

  • ローカル DB
  • 一時ディレクトリ
  • test host
  • 専用の test environment
  • 契約を固定した fake service

の組み合わせが扱いやすいです。

7. 実務でのおすすめ構成

比率に絶対の正解はありません。
ただし、かなり汎用的に使えるのは、次の 3 層です。

主力 何を置くか
コア層 ユニットテストを厚く 業務ルール、状態遷移、入力検証、エラー分類
境界層 狭い結合テストを置く DB、ファイル、HTTP、serializer、DI、設定、COM、権限
全体層 少数のスモーク / E2E 起動確認、主要フロー、重大障害の再発防止

感覚的には、数で厚くなるのはユニットテスト、境界の濃さで厚くなるのが結合テスト です。

おすすめの進め方はこうです。

  1. まず、アプリの境界を列挙する
  2. ロジックを外界から切れる形に寄せる
  3. 境界ごとに「最低 1 本の happy path」と「代表的な failure path」を置く
  4. 全体の通しは本数を絞る
  5. バグが出たら、そのバグを最小コストで再現できる層へテストを追加する

最後の 5 が大事です。

  • ルールの誤りなら、ユニットテストを追加する
  • SQL / binding / 設定 / 権限 / 登録の誤りなら、結合テストを追加する
  • 起動や配布を含む障害なら、スモークや E2E を追加する

この増やし方をすると、テストの責務がぶれにくくなります。

8. 迷ったときに最後に見る 5 問

最後に、迷ったときのチェック用に 5 問へまとめます。

  1. in-memory の fake で置き換えても、確認したい意味は残るか
    • 残るなら、ユニットテスト寄りです。
  2. 壊れたときに疑うのは、ロジックではなく接続や設定ではないか
    • そうなら、結合テスト寄りです。
  3. DB / ファイル / serializer / DI / route / model binding / OS / 権限 / bitness / thread が主題ではないか
    • そうなら、結合テスト寄りです。
  4. 大量の入力パターンを高速に回したいか
    • そうなら、ユニットテスト寄りです。
  5. そのテストが落ちたとき、何を直せばよいかがすぐ分かるか
    • 分からないなら、テストの層が混ざっています。

この 5 問で整理すると、「何となく本物に近いから結合テスト」「何となく速いからユニットテスト」という雑な決め方を避けやすくなります。

9. まとめ

ユニットテストと結合テストの境界は、コードの置き場所ではなく、何の不確実性を減らしたいか で決めるのがいちばん実務的です。

要点をまとめると、次のようになります。

  • ユニットテストは判断のテスト
  • 結合テストは接続のテスト
  • 分岐の総当たりはユニットテスト
  • フォーマット、配線、環境、時間は結合テスト
  • 全体の通し確認は少数のスモーク / E2E で押さえる

いちばん避けたいのは、

  • mock で本物との接続まで証明した気になる
  • 結合テストで全部の分岐を回そうとする
  • ユニットテストと結合テストの責務を混ぜる

の 3 つです。

迷ったら、その不具合は「判断」が壊れるのか、「接続」が壊れるのか を先に見てください。
この 1 問で、かなりのケースは整理できます。

10. 関連記事

11. 参考資料

関連トピック

このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。

このテーマがつながるサービス

この記事は次のサービスページにつながります。近い入口からご覧ください。

ブログ一覧に戻る