「ここは大丈夫」と思ったところほど危ない
結合テストをしているときに、少し怖いなと思う瞬間があります。
それは、画面や機能を触る前から、
- この処理は単体テストで通っている
- コードの構造的に、このパターンは起きない
- この画面遷移は普通に使えば問題ない
- 今回の修正範囲とは関係ない
と、自分の中で先に結論を出してしまう瞬間です。
開発者としてコードを書いていると、内部の仕組みを知っています。
どの条件分岐を通るのか、どのデータが入る想定なのか、どこまでテスト済みなのかも分かっています。
その知識はもちろん強みです。
ただ、結合テストではその知識が邪魔になることもあります。
「ここは大丈夫」と思って確認を薄くした場所に、意外とバグが残っている。
自分は何度かそういう場面を見てきました。
今回は、開発からテストまで同じ人が担当するときに、どうすれば結合テストで想定外のバグを見つけやすくなるのかを整理してみます。
結合テストは、部品の正しさではなく流れの違和感を見る
単体テストは、部品の正しさを確認するものです。
一方で結合テストでは、複数の部品がつながったときに、実際の流れとして問題がないかを見ます。
Railsの公式ガイドでも、インテグレーションテストはアプリケーションの複数の部分がどう相互作用するか、重要なワークフローを確認するためのものとして説明されています。
Testing Rails Applications - Ruby on Rails Guides
ここで大事なのは、「それぞれの処理が正しいか」だけではなく、「流れとして不自然ではないか」を見ることだと思っています。
例えば、注文機能で考えるとします。
- 商品をカートに入れる
- 数量を変更する
- クーポンを使う
- 配送先を変更する
- 決済へ進む
- エラーが出たあとに戻る
それぞれの処理だけを見ると問題がなくても、組み合わせるとおかしくなることがあります。
クーポン適用後に数量を変えると金額がずれる。
決済エラー後に戻ると、配送先だけ古い状態に戻る。
権限のないユーザーがURL直打ちで途中画面に入れてしまう。
こういうバグは、コードを読んでいるだけだと見落としやすいです。
結合テストでは、コードの理解よりも先に、実際の利用の流れを疑う姿勢が必要になると感じています。
開発者の知識は、テスト観点を狭くすることがある
開発者が自分で結合テストをすると、無意識に「正しい使い方」をしてしまいます。
ボタンは想定された順番で押す。
入力値も自然なものを入れる。
エラーになりそうなルートは、コード上ありえないと判断して触らない。
これは性格の問題ではなく、コードを知っているから起きることです。
実装者は、内部の都合を知っています。
- このフォームはこの順番で使われる想定
- この値はバリデーションで弾かれる想定
- このAPIはこの画面からしか呼ばれない想定
- この状態は通常の操作では作られない想定
こうした想定が積み重なると、テストが「実装者にとって自然な操作」だけに寄っていきます。
でも実際のユーザーは、コードの都合を知りません。
戻るボタンを押します。
二重送信します。
途中でページを閉じます。
URLを直接開きます。
エラー画面からもう一度試します。
結合テストで見たいのは、こういう「コードの外側から見た動き」です。
以前書いた バグを減らすテスト観点。ループ処理・権限・性能を先に見る でも触れましたが、バグは「自分が嫌だなと思うところ」に出やすいです。
結合テストでも同じで、面倒に感じる操作、確認を飛ばしたくなる状態、再現手順を書くのが少しだるい流れほど、ちゃんと見たほうがいいのだと思います。
抽象度を一つ上げて、ユーザーの行動として見る
想定外のバグを見つけたいとき、まずやるとよいのは抽象度を一つ上げることです。
コードやメソッド単位ではなく、「ユーザーがやりたいこと」として機能を見ます。
例えば、実装者の視点だと、
-
POST /ordersが成功するか - パラメータが正しく保存されるか
- バリデーションエラーが返るか
を見たくなります。
もちろん見ておきたいです。
ただ、結合テストではもう少し上から、
- ユーザーは迷わず注文完了まで進めるか
- エラーが出たあと、何を直せばよいか分かるか
- 戻る、再送信、別タブ操作で状態が壊れないか
- 途中でログインが切れたときに不自然な画面にならないか
を見る必要があります。
この視点に切り替えるだけで、確認する場所が変わります。
コード上の成功パターンだけではなく、画面のつながり、表示文言、操作順序、エラー後の復帰まで気になるようになります。
結合テストは「機能が動いたか」を見るだけではなく、「ユーザーがその流れを使える状態になっているか」を見るものだと考えると、見落としが少し減ります。
抽象度を一つ下げて、データの状態を見る
一方で、抽象度を上げるだけでは足りません。
今度は逆に、抽象度を一つ下げて、内部の状態も見ます。
画面上は成功しているように見えても、データがおかしくなっていることがあります。
- ステータスだけ更新されて、履歴が残っていない
- 関連レコードが作られていない
- 古いキャッシュが残っている
- 通知だけ送られて、画面上の状態とずれている
- エラー時に途中まで保存されている
こういう問題は、画面だけを見ていると気づきにくいです。
だから結合テストでは、ユーザー目線で流れを見たあとに、必要なところだけ内部状態を確認するのが良さそうです。
たとえば、
注文完了後に見ること
- 画面に完了メッセージが出ているか
- 注文ステータスが期待通りか
- 明細の金額が画面表示と一致しているか
- クーポン利用履歴が残っているか
- 通知メールの送信対象が正しいか
- 失敗時に中途半端な注文が残っていないか
くらいまで見ると、ただの画面確認よりも実務に近づきます。
上からユーザーの流れを見る。
下からデータの状態を見る。
この上下の往復が、結合テストでは欠かせないと感じています。
過去のバグを、今回のテスト観点に持ち込む
もう一つ大事なのは、過去のバグを今回のテストに持ち込むことです。
以前似たような機能で起きたバグは、別の画面でも起きる可能性があります。
- 二重送信でデータが重複した
- 権限チェックが画面側だけに寄っていた
- エラー時に入力値が消えた
- 一部のステータスだけ更新漏れがあった
- 既存データでは問題なく、新規データだけ壊れた
こういう経験は、ただの思い出にしておくのはもったいないです。
結合テスト前に、過去の不具合やレビューコメントを少し見返すだけでも、観点が増えます。
このあたりは 結合テストの質は誰が書くかで変わる にも近い話です。
テストケースは、書いた人の経験や違和感が出ます。
そのため、過去に痛い目を見た観点をチームや未来の自分に残しておきたいです。
「前にこのパターンで壊れたから、今回も見る」
これは地味ですが、実務では役に立つ考え方だと思っています。
テスト前に小さな観点メモを作る
結合テストを始める前に、いきなり画面を触り始めると、どうしても確認が流れ作業になります。
私は、先に小さな観点メモを作るのが良さそうだと感じています。
大げさなテスト仕様書ではなく、次のようなメモで十分です。
# 結合テスト観点メモ
## ユーザーの流れ
- 最短ルートで完了できるか
- 戻る、再送信、途中離脱で壊れないか
- エラー後に復帰できるか
## データの状態
- 作成、更新、削除の結果が画面と一致しているか
- 関連レコードが正しく作られているか
- 失敗時に中途半端なデータが残らないか
## 過去のバグ
- 似た機能で起きた不具合はないか
- レビューで何度も指摘された観点はないか
- 権限、境界値、二重操作を見たか
## 今回特に怖いところ
- 仕様が曖昧なところ
- 実装が複雑になったところ
- 自分が「多分大丈夫」と思っているところ
最後の「自分が多分大丈夫と思っているところ」は、少し嫌な項目です。
でも、ここが一番大事かもしれません。
自分の中で確認を省略したくなる理由がある場所は、すでに先入観が入っています。
そこを一度メモに出すだけで、テストの目が変わります。
結合テストは、疑うためではなく安心して出すためにやる
テストというと、どうしてもバグ探しのイメージが強くなります。
もちろんバグを見つけることも見ます。
ただ、自分は最近、結合テストは「疑うため」だけではなく、「安心して出すため」にやるものだと考えています。
自分の実装を信用しない、というよりも、
自分の見落としやすい場所を知った上で、確認の仕組みを置く。
そのほうが健全です。
「無意識に良いだろう」と思って書いた実装は、だいたい良くない でも書いたように、実装では意図を言語化することが必要です。
テストも同じで、「なぜこの観点を見るのか」を言葉にできると、確認の質が上がります。
なんとなく画面を触るのではなく、
- この順番で操作される可能性があるから見る
- この状態は過去に壊れたから見る
- この処理はデータの整合性が怖いから見る
- ここは自分が大丈夫だと思い込んでいるから見る
と理由を持つ。
それだけで、結合テストは実務に近いものになります。
明日からできること
結合テストで想定外のバグを見つけるために、特別な技術が必ず必要というわけではありません。
まずは次の3つだけでも始められます。
- テスト前に「今回怖いところ」を3つ書く
- ユーザーの流れとデータの状態を両方見る
- 過去のバグを1つだけ今回の観点に混ぜる
特におすすめなのは、「今回怖いところ」を先に書くことです。
怖いところは、実装が複雑な場所かもしれません。
仕様が曖昧な場所かもしれません。
自分が確認を省略したくなっている場所かもしれません。
そこを先に言葉にしておくだけで、結合テストの密度は変わります。
結合テストは、単体テストでは見つからないバグを探す最後の砦ではあります。
でも、それ以上に、リリース前に自分の思い込みを外すための時間でもあると思います。
開発者目線を一度外す。
ユーザーの流れで見る。
必要なところだけ内部状態まで潜る。
過去のバグを今回の観点に持ち込む。
このあたりを意識して、今後の結合テストでも「ここは大丈夫」と思った場所ほど、少しだけ丁寧に見ていきたいです。