- 技術 💻
単体テスト・結合テストの落とし穴8選|カバレッジ・モック
テストって、「通ればOK」で終わりがちなんですよね。
でも、カバレッジ100%でもバグは残るし、たまに落ちるテストには全部理由があります。今回は、全部通ったのに本番で落ちる理由を、単体テスト(UT)と結合テスト(IT)それぞれから8つ集めました。1つでも知らなかったら儲けものです。
①【単体テスト】カバレッジ100%でも、バグは残ります
カバレッジは「テストがそのコードを通ったか」を測る指標です。ここで大事なのが、通ったことと、正しさを確かめたことは別だという点。
極端な話、こんなテストでもカバレッジは上がります。
@Test
void test() {
calc.add(1, 2); // 呼ぶだけ。検証(assert)が無い
}
コードは通ったので、カバレッジ上は「テスト済み」。でも何も確かめていません。さらに、通った行の中でも「境界値」や「異常系」を試したかは、カバレッジには表れないんですよね。
AIにテストを書かせることが増えたいま、ここは特にチェックしたいポイントです。カバレッジは、テストが足りない場所を見つける道具。品質の証明書ではないんです。
②【単体テスト】モックを作り込むほど、テストの価値は下がります
外部依存を切り離すためのモック。便利なんですが、罠があります。
「この呼び出しにはこの値を返す」と作り込んでいくと、最終的にモックの振る舞いをテストしている状態になるんですよね。本物の実装がどう変わっても、テストは通り続けます。
さらに厄介なのが、モックは「呼び方」に依存するということ。中身のリファクタリングをしただけでテストが壊れるなら、それは実装に縛られすぎているサインです。
だから定石は、外部との境界にだけモックを置く。内側は本物を通す。「テストが真っ赤になるからリファクタリングできない」を防ぐ考え方です。
③【単体テスト】テストが書きにくいのは、設計からのメッセージです
「このメソッド、テストしづらいな」と感じたとき。テストの技術不足ではなく、設計が教えてくれていることが多いんです。
| テストで感じる書きにくさ | 設計からのメッセージ |
|---|---|
| 準備するデータが多すぎる | 責務を持ちすぎている |
| 中で現在時刻や乱数を直接呼んでいる | 結果を固定できない |
| privateメソッドをテストしたくなる | クラスを分けるべきかもしれない |
とくに現在時刻の直接参照は定番で、外から渡せる形にするだけでテストが一気に書けるようになります。
テストのしやすさは、設計の良さの指標。書きにくさを感じたら、設計を疑う合図なんですよね。
④【単体テスト】テストコードには、DRYを持ち込みすぎない
「同じ処理は共通化する」——製品コードの鉄則ですよね。テストコードでは、少し違います。
共通化を進めすぎると、テストが落ちたときに何が起きたのか読み解けなくなるんです。共通メソッドをいくつも辿らないと、前提条件が分からない。
テストコードで優先されるのは、1つのテストを読めば、前提・実行・検証が全部分かることです。多少の重複は、読みやすさのための必要経費なんですよね。
製品コードの正解が、テストコードでは不正解になることがある。これは知らないと逆をやってしまいます。
⑤【結合テスト】「たまに落ちる」には、必ず理由があります
Flakyテストと呼ばれる、実行するたびに結果が変わるテスト。原因はだいたい4つに絞れます。
- 非同期処理の待ち不足(固定の待ち時間で誤魔化している)
- 実行順への依存(前のテストが作ったデータに頼っている)
- 共有データの汚染(DBを掃除せずに次へ進んでいる)
- 時刻や乱数(日付をまたぐと落ちる、月末だけ落ちる)
いちばん怖いのは、「再実行したら通ったのでヨシ」で放置することです。
チームが赤を無視するようになると、テストは信号としての役目を失います。本物のバグを見逃す入り口になるんですよね。
⑥【結合テスト】単体では通るのに、並べると落ちます
「私のテストは通るんです」——結合テストで最頻出のセリフです。
原因は、テスト同士が同じ場所を共有していること。同じDBのテーブル、同じファイル、同じ静的変数。
さらにやっかいなのが、実行順は保証されないことがあるという点。ツールによっては順序が変わったり、並列実行されたりします。
だから定石は、各テストが自分で後始末をすること。データを毎回作り直す、トランザクションを毎回ロールバックする。
「他のテストの結果に依存しない」——これが結合テストのいちばんの作法です。
⑦【全体】テストは、ピラミッド型が基本です
単体テストと結合テスト、どれくらいの比率で書けばいいのか。有名な指針が、テストピラミッドです。
土台に単体テストを厚く、上に行くほど少なく。上(結合・E2E)に行くほど、実行が遅く、環境に左右されやすく、壊れやすいからなんですよね。
逆に上が厚くなった形は、アイスクリームコーンと呼ばれます。動かすたびに何十分もかかり、しかもよく落ちる。心当たりのある方も多いんじゃないでしょうか。
ただし近年は「形にこだわりすぎず、リスクの高いところに厚く」という考え方も広がっています。大事なのは形そのものより、遅くて脆いテストに依存しすぎないという原則なんです。
⑧【全体】バグは、遅く見つかるほど高くつきます
古くから言われている経験則があります。バグの修正コストは、発見が遅れるほど跳ね上がる。
設計中に気づけば直すだけ。単体テストなら数分。結合テストなら他チームを巻き込む。本番なら、調査・報告・改修・再テスト・お詫びまでが一式です。
近年の研究では「昔言われたほど極端な倍率ではない」という指摘もありますが、遅いほど高くつくという方向自体は変わりません。
だから単体テストは、面倒な作業ではなくいちばん安い場所でバグを捕まえる投資なんですよね。この順番で捕まえるための仕組みが、テストの階層なんです。
番外編:知っておくと会話が楽しくなる話
「テストがないコードはレガシーコード」
書かれたのが昨日でも、テストが無ければレガシー。マイケル・フェザーズの『レガシーコード改善ガイド』による定義です。年数ではなく、安心して変更できるかどうかでレガシーを定義したのが革命的でした。
赤 → 緑 → リファクタリング
先に失敗するテストを書き(赤)、通し(緑)、整える。テスト駆動開発(TDD)のリズムです。最初にわざと失敗させるのは、「テストがちゃんと落ちること」を確かめる意味があるんですよね。
カバレッジには種類があります
行を通ったか(行カバレッジ)と、条件の分岐を両方通ったか(分岐カバレッジ)は別物です。同じ「80%」でも、どちらの話かで意味が変わります。
RJCでも、テストと向き合っています
RJCは要件定義から設計・開発・検証・運用まで通して携わっています。保険、金融、決済、交通。止まると生活に影響が出るシステムばかりです。
だからテストは「最後にやる作業」ではなく、品質を作り込む工程そのものなんですよね。
研修でもJUnit5を扱いますし、自分の手で設計・実装・テストを回すサイクルを土台にしています。テストを語れる人は、どの現場でも重宝されるんです。
よくいただくご質問
Q. まずどれから意識すればいいですか?
A. ①のカバレッジかなと思います。数字を追う姿勢から、境界値と異常系を見る姿勢に変わると、品質が一段上がります。
Q. テストは、どこまで書けばいいですか?
A. 「壊れたら困る順」が現実的だと思います。全部を100%にするより、リスクの高いところを厚くする方が費用対効果が高いんですよね。
Q. Flakyテストを見つけたら、どうすべきですか?
A. 消すか直すか、どちらかを選ぶのが定石です。放置がいちばん良くないんですよね。⑤の4原因のどれかに当てはまることがほとんどです。
Q. 未経験からでも、テストは書けるようになりますか?
A. なります。むしろ最初にテストの書き方を覚えた人ほど、安心して大胆にコードを直せるようになるんですよね。
「全部緑」は、まだ何も証明していません
今日の8つを並べてみて思ったのが、これでした。
テストが全部通っている状態は、「確かめた範囲では問題がない」という意味でしかありません。何を確かめて、何を確かめていないのか。そこを言葉にできる人が、いちばん品質に貢献するんですよね。
緑を増やすことより、緑の中身を語れることなのかなと思います。
みなさんが踏んだテストの「ほぉー」、よかったら教えてください。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。