1 結合テストの概要

1.1 目的と役割

1.1.1 単体テストとの違い

単体テストは、個々の関数、クラス、または小さなモジュールが設計どおりに振る舞うことを、入力と出力を中心検証する。これに対し結合テストは、複数の部品が連携したときに、入出力の契約インターフェース)やデータの受け渡し、制御の受け渡しが整合するかを確認する。単体では見えにくい型・形式の不一致、通信経路での欠落、整合性制約の違反などが対象となる。

1.1.2 「つなぎ目」で起きやすい不具合

部品同士の接点では、仕様の解釈差や実装の前提がずれることで不具合が顕在化しやすい。代表例として、入出力の変換(シリアライズや文字コード単位変換)、レスポンスのフォーマット差、例外の伝播方法の違い、タイムアウト時の扱い、リトライの重複実行、同時実行に伴う競合などが挙げられる。また、ログ監査情報の整合性がとれていない場合、運用時の追跡可能性が低下し、結果として不具合の再現・原因特定が難しくなる。

1.2 対象範囲の考え方

1.2.1 コンポーネント単位の結合

対象は「どこまでを一体として扱うか」で決まる。コンポーネント単位の結合では、入力受領から内部処理、出力生成までの一連が、隣接するコンポーネントと整合するかを重点的にみる。依存関係が増えるほど試験対象は広がるが、失敗原因が複雑化するため、境界を適切に切ることが重要となる。

1.2.2 サービス・モジュール間の連携

サービスまたはモジュール間の連携を対象にする場合、通信やプロトコル、契約(API仕様)、データモデル、認可認証冪等性など、分散した振る舞いの整合性が焦点になる。特に、リクエストの追跡ID、相関情報の受け渡し、エラーコード体系、部分失敗時のリカバリ戦略といった「連携で決まるルール」が検証項目となる。

1.3 テスト方針と観点

1.3.1 成功系(正常系)検証

正常系では、主要な業務フローや代表的な経路が、入力条件に対して期待される結果を返すことを確認する。ここでは、レスポンスの構造、状態遷移、永続化結果、参照整合、後続処理の起動条件など、複数部品をまたいだ一貫性を重視する。成功時における「見かけの正常」ではなく、内部状態や関連データまで含めて確認するのが一般的である。

1.3.2 失敗系(異常系)検証

異常系では、想定される失敗の種類ごとに、連携全体としてのふるまいを検証する。具体的には、無効入力、必要項目の欠落、存在しない参照、権限不足、通信障害、遅延、タイムアウト、外部依存の不達などを扱う。各失敗に対して、エラーの形(コード、メッセージ、分類)、再試行可否、ロールバックや補償、部分更新の取り扱いが仕様どおりかを確認する。

1.3.3 非機能要件の確認範囲

結合テストでは非機能要件を全面的に扱うとは限らないが、最低限の品質を担保する観点が含まれることがある。たとえば応答時間の上限、同時実行時の振る舞い、接続の再確立、ログ出力の完全性など、連携に起因する性能・信頼性欠陥を早期に検知する目的で、軽量な確認を追加する。


2 設計と準備

2.1 テストケース設計

2.1.1 連携シナリオの洗い出し

連携テストは「何がつながっているか」を明確化し、それぞれの組み合わせで起きうる経路を列挙してシナリオ化する。ここでは、単体の動作ではなく、部品間の契約やデータ受け渡しがどのように成立・崩壊するかを中心に据える。

2.1.1.1 正常フローの組み合わせ

正常フローは、利用者操作や上位サービスからの呼び出しに沿って、入力パラメータ、参照キー、前提状態(事前に存在するデータ)、後続処理の有無などを組み合わせて設計する。境界条件として、最小値・最大値、空リスト、欠落しないが特殊な値(ゼロ、負値の扱いが定義されている場合など)も含めると、連携の取りこぼしが減る。

2.1.1.2 例外・タイムアウト・リトライ

異常シナリオは、例外の種類だけでなく、時間要素を伴う状況としてタイムアウトやリトライを具体化する。たとえば、依存先が遅延した際の待機、タイムアウト発生後の再試行、再試行時の冪等性確保、部分的に成功した場合の整合性維持などを段階的に切り分けてケース化する。連携では「再試行するときの契約」が重要であり、成功・失敗の境界を曖昧にしないことが必要となる。

2.1.1.3 データ整合性の確認観点

データ整合性の検証では、更新系の結果が正しいテーブルや状態に反映されているか、参照先が矛盾しないかを確認する。具体的には、トランザクションの境界、整合性制約(外部キー、ユニーク制約など)、イベント発行と書き込み順序、重複投入の検出といった観点をチェックする。さらに、読み取り側が期待するビューやキャッシュ更新のタイミングも含める。

2.2 テストデータと環境

2.2.1 参照データの準備

参照データは、検証する業務ルールに直結する。必要なマスタ情報、初期状態、履歴データ、権限に関する情報などを、テスト可能な粒度で準備する。データの意味が明確で再現性が高いほど、失敗時の原因切り分けが速くなる。可能であれば、生成手順をコード化し、手作業依存を減らす。

2.2.2 異なる環境差の吸収(設定・依存)

テスト環境では、外部サービスやミドルウェアの設定差が結果に影響することがある。たとえばエンドポイント、証明書、タイムゾーン、暗号化方式、スキーマバージョン、機能フラグなどで差が生まれる。これらを差分管理し、テストが依存先の偶然に左右されないように調整することで、安定性と再現性を確保する。

2.3 モック・スタブ・代替手段

2.3.1 モックの使い分け

モックは、相手の振る舞いを制御して観測可能にするために用いる。呼び出し回数、引数の検証、返却値や例外の注入などに適する。ただし、モックが「実際と違う振る舞い」を作り込むと、本来検知すべき連携不具合が埋もれる。契約に基づき返却形式やエラー表現を忠実に合わせることが前提となる。

2.3.2 スタブによる段階的結合

スタブは、相手の機能を最小限の実装で置き換える手段として使われる。開発の段階では、上流・下流のどちらかが未完成でも結合テストを進めやすくする目的がある。スタブ側で本格的な整合検証まで行うかどうかは、段階の位置づけとリスク評価に依存する。実装が進んだタイミングで、スタブから実装へ段階的に切り替える設計が望ましい。

2.3.3 実環境との切り替え戦略

切り替え戦略では、どの依存先をどの程度実物に近づけるかを決める。重要な契約点や障害時挙動が連携の要点である場合は、実環境に近い条件で実行する割合を高める。一方で、頻繁に変動する外部要因は隔離し、再現性を確保する。段階的な実行(軽量な結合、より厳密な結合)を組み合わせることで、コストと検出能力のバランスを取る。


3 実施手順

3.1 実行順序と段階化

3.1.1 先に固定する境界(インターフェース)

最初に確定させるべきは、呼び出し規約やデータ形式などのインターフェースである。ここが揺れると、テストケースや期待値が何度も作り直しになる。インターフェースが確定した後、実行可能な結合形に落としていき、仕様の齟齬があれば早期に検知して修正へつなげる。

3.1.2 部分結合から全結合へ

段階化では、まず少数の部品で構成し、観測可能な単位で検証を進める。次に、隣接する連携を増やして範囲を拡大し、最終的に主要な経路を全結合として確認する。部分結合では失敗原因が局所化しやすく、暫定的な不具合修正の速度が上がる。全結合では、複数の要因が同時に絡むため、ログや追跡情報の整備が特に重要になる。

3.2 ログ・トレーシング

3.2.1 期待値と実測値の突合

失敗時に有効なのは、期待値と実測値を同じ粒度で突合できる仕組みである。レスポンスのフィールド単位、永続化された値、外部呼び出しの要約などを対応づけることで、誤差の原因がデータ変換、例外伝播、更新順序のどこにあるかを短時間で特定できる。成功時も一部の検査を行い、レポートに残すことで回帰検知を容易にする。

3.2.2 相関情報による追跡

分散した連携では、単一の処理が複数サービスにまたがる。そこで、相関IDやトレースIDのような識別情報を起点から終端まで伝播させる。これにより、要求の流れ、途中での分岐、失敗時の伝播経路を時系列で追える。追跡可能性が高いほど、再現環境の構築や原因究明の手間が減る。

3.3 成果物の管理

3.3.1 テスト結果の記録

記録には、実行日、対象構成、テストケース名、入力条件、観測結果、差分(期待と実測の不一致点)、失敗の再現手順が含まれるのが望ましい。加えて、環境情報や依存先のバージョンも残すと、同じ条件での追試が可能になる。結果の保存粒度が低いと、後から意思決定に必要な情報が欠ける。

3.3.2 再実行しやすい構成

再実行しやすさは、失敗調査の速度を左右する。固定された初期データ、構成管理された設定値、乱数を排した入力生成、テスト用の隔離などが有効である。また、並列実行時の相互干渉を抑えるため、リソース名の衝突を避ける設計や、後始末の確実化も実施段階で重要になる。


4 成績評価と改善

4.1 合否基準と指標

4.1.1 テストカバレッジの扱い

結合テストのカバレッジは、コード行数だけで評価しないことが多い。代わりに、主要シナリオの網羅度、代表的なインターフェース組み合わせの通過、例外パスの存在など、連携観点での到達度を指標化する。カバレッジの見える化は、追加すべきケースの方向性を示す役割を担う。

4.1.2 不具合検出率と傾向

不具合検出率は、検出された不具合の件数だけでなく、失敗の種類の内訳を見て改善の優先度を決める。たとえば整合性違反が多いのか、タイムアウト関連が多いのか、エラー表現の形式差が多いのかを整理する。傾向が見えると、インターフェースの見直し、仕様書の明文化、テストデータの修正など、根本原因に近い手当てが可能になる。

4.2 バグの切り分け

4.2.1 原因切り分けの手順

切り分けでは、まずログと追跡情報から、どの部品で期待から逸脱したかを特定する。次に、インターフェースの契約(入力形式、出力形式、エラー仕様)と、前提状態(初期データ、権限、状態遷移)を突き合わせる。必要に応じて、部分結合の再現実行で範囲を狭め、責務の所在を判断する。最後に、修正後の再実行で同じ経路が安定して通ることを確認する。

4.2.2 連携仕様の曖昧さの解消

連携仕様が曖昧な場合、テストは「何が正しいか」を決められず、不具合も繰り返し発生しやすい。そこで、エラーコードの定義、例外の伝播方法、タイムアウト時の処理、データの整合性条件、再試行のルールを文章と例で明確化する。仕様の明確化は、テストの期待値を安定させ、将来の改修でも揺れを減らす効果がある。

4.3 継続的インテグレーションへの組み込み

4.3.1 自動化の範囲

継続的インテグレーション(CI)に組み込む際、自動化の範囲を決める必要がある。一般に、主要な正常系と主要な異常系を優先して自動化すると、回帰検知が早くなる。外部要因に強く依存するケースは、安定性を考慮して頻度や条件を調整する。自動化の結果が再現可能であることが、運用上の信頼につながる。

4.3.2 実行時間と安定性の最適化

実行時間が長いとCIのフィードバックが遅れ、開発のリズムに悪影響が出る。安定性を高めるには、タイムアウト閾値の適正化、依存先の負荷変動への対策、並列実行時の競合回避などが有効である。テストを層として分け、迅速に走るものと、より厳密な確認を行うものを組み合わせることで、検出能力を保ちながらコストを抑える。