ユニットテストのコードカバレッジ(テストカバレッジ。ステートメントカバレッジやC0、C1など)は、不適切な運用が根強く見られます。多いのが、コードカバレッジの確保だけをテストの十分性目標にして、まずいテストを書いてしまうパターンです。
今回はこのコードカバレッジについて、現代的な開発を支えるための適切な運用について解説します。
コードカバレッジのみを直接のテストの十分性の目標にしてはいけない
結論から言うと、まずユニットテストは以下を目標として作成します。
- ふるまいの仕様が実現されているか確認する
- プログラマが感じる品質リスク(いわゆる不吉な臭い等)が許容できる水準であることを確認する
- 法規制対応など、外部からのテスト要求に対応する
コードカバレッジは、上記目標を達成することで副次的に確保されることを目指します。
注意点として、コードカバレッジの確保のみを直接の目標にすると弊害が大きくなります。あくまで上記目標の達成が妥当かの参考指標、副次的目標として活用します。参考指標としての活用例を以下に示します:
- ふるまいの仕様に対して網羅的なテストを作ったのに、コードカバレッジが低い場合は、以下の問題がないか調べる
- 仕様からはずれた冗長なコードがある。極端な例では、実行できないデッドコードがある
- 明示化されていない暗黙の仕様がある。それがテストの不足を引き起こしている
- コードのテスト容易性に問題がある
- 継続的に実行しているテストのコードカバレッジが下がった場合は、以下の問題がないか調べる
- 適切なテストを書かないまま、プロダクトコードを追加・変更している
- テストが意図通り実行されていない可能性がある。例えばテストのブロックが発生している
- Flaky Testといったテストの信頼性の問題が発生している
注意事項として、コードカバレッジの確保が無価値というわけではありません。まず上記のような参考指標として活用できます。またPythonといったインタプリタ言語では、高いコードカバレッジを確保したテストは、最低限実行できるコードであることを保証する、コンパイルチェックの代替となります。リグレッションテストとして信頼してよいかの補助指標にもなります。
あくまで、コードカバレッジだけを目標としてはいけない、というのが要点です。
コードカバレッジのみを直接の目標にしてはいけない理由
アジャイル、CI/CD、DevOpsといった、継続的に・反復的にソフトウェアを作りこむ現代的な開発では、ソフトウェア開発の持続可能性・保守性の確保が重要です。テストコードの実装でもそれは同様であり、次のようなテストの品質の確保が重要になります。
- 冗長さがなく、コンパクトなコードで的確にテストの目的を満たしている。
- テストの意図が明確で、変更時にテストをどのように保守すればよいか理解できる。
- テストが安定していて、継続的にソフトウェアを変更する中でも壊れにくい。
コードカバレッジのみを直接の目標にしてはいけない理由は、上記の品質確保を阻害するためです。具体的には次のような問題を発生させます:
- コードの仕様とは無関係のテストを誘発する。テストコードを読んでもなんのためのテストか分かりにくい状態を引き起こす。またソフトウェアの価値に貢献しない無駄なテストを許してしまう。極端な例として(過去に見たものですが)Assertionのない実行するだけのテストや、testcase_1、testcase_2のような連番で構成されるテストメソッド名のようなものを生んでしまう。
- テストが構造に強結合し、コードの変更に弱くなる。例えばふるまいを変更させないリファクタリングでもテストが壊れることがある。
このような問題の存在から、コードカバレッジは、他の目標達成の過程で達成を目指す、副次的な目標として扱うべきです。
コードカバレッジが不足している場合にどうするか
テストのないレガシーコードを扱ったり、テストが壊れた状態をリカバリしようとしたりする場合などで、コードカバレッジが低い状態に対応しないといけない場面が度々あります。
そこではCover&Modifyのためにテストの網羅性改善が必要になりますが、そうした場合でもコードカバレッジのみ見てテストをつぎ足すアプローチでは前述の問題を誘発しがちです。
手間が増えるため十分に推進できない場面がありますが、理想的には、次のアプローチを取るのが無難です。
- テストの目的に立ち戻り、ふるまい仕様や品質リスク等に対してどのようなテストケースが必要か、テスト設計する
- 上記に基づいてテストコードの整理や追加を行う
- テストコードの整理や追加の副次的効果として、コードカバレッジが改善していることを確認する
あくまでテストの目的に立ち戻り、それに基づいてテスト設計を整理するのが基本アプローチとなります。