はじめに
本記事はQmonus Value Streamの投稿キャンペーン記事です。
この記事は、「どうしてあなたのアラートは見られないのか」と関連しています。
この記事単体で理解することも可能ですが、概要編、理論編を読むとより深く理解することが可能です。
いいね、ストックをよろしくお願いします!
テストカバレッジ
テストカバレッジ
ソフトウェアテストにおいて、プログラムコードがテストによってどの程度カバーされているかを示す指標
高いテストカバレッジは、ソフトウェアの品質と信頼性を向上させる重要な要素となります。
テストカバレッジには様々な種類が知られています。ここでは、C0、C1、C2カバレッジを紹介します。これらは、コードの異なる側面をテストする方法を表しています。
C0カバレッジ
C0カバレッジは、命令網羅とも呼ばれます。これは最も基本的なカバレッジの形式です。
- 定義:プログラム内のすべての実行可能なステートメントが少なくとも1回実行されることを確認する
- 目的:実行するだけで失敗するようなバグをなくすことができる
- 限界:分岐や条件文の全てのパターンをテストするわけではない
C1カバレッジ
C1カバレッジは、分岐網羅として知られています。C0カバレッジよりも厳密なテスト基準を提供します。
- 定義:プログラム内のすべての分岐(if文、switch文など)が真と偽の両方の結果で少なくとも1回実行されることを確認する
- 目的:条件分岐の両方のパスがテストされることを保証し、より包括的なテストを実現する
- 限界:C0カバレッジよりも高品質なテストを提供するが、複雑な条件式の全ての組み合わせをカバーするわけではない
C2カバレッジ
C2カバレッジは、条件網羅として知られています。
- 定義:プログラム内のすべての条件式において、各条件の真偽の組み合わせが少なくとも1回はテストされることを確認する
- 目的:複雑な条件式の全ての可能な組み合わせをテストし、より詳細で網羅的なテストを実現する
- 限界:非常に厳密なテスト基準であり、大規模なシステムでは完全なC2カバレッジの達成は困難なことが多い
例えば、if (A && B) という条件があった場合、C2カバレッジでは以下の4つの組み合わせすべてがテストされます。
- A が真、B が真
- A が真、B が偽
- A が偽、B が真
- A が偽、B が偽
その他のカバレッジ
他にも、複合条件網羅やパス網羅など、様々なカバレッジが提案されており、適切なツールを使うことで測定することができます。
しかし、どのカバレッジも以下のいずれかの問題を抱えています。
- 簡易的過ぎてバグを見逃す(偽陰性が多い)
- 網羅的過ぎて達成不可能(コストがかかりすぎる)、リファクタリング時に無駄なテストケースが増える(リファクタリング耐性が低い)
テストの用途
テストカバレッジとは、テストの効率性、網羅性を評価するものです。そのため、まずソフトウェア開発において、テストとはどのような役割を持っているのかを改めて考えてみましょう。
品質保証
ソフトウェア開発におけるテストの主要な目的の一つは品質保証です。品質保証は以下のような側面を持ちます。
-
バグの早期発見(悪くないことの判定)
- テストを通じて、開発の早い段階でバグを発見し修正する
- 後の段階での修正コストを大幅に削減する
-
要件の充足確認(正しいことの判定)
- ソフトウェアが仕様書や要件定義に記載された機能を正しく実装しているかを確認する
- テストの策定を通じて、仕様自体の妥当性を考えることにもなる
- ユーザーの期待に沿った動作をしているかを検証する
-
回帰テスト(壊れないことの判定)
- 新機能の追加や既存機能の修正後も、以前の機能が正常に動作することを確認する
- これにより、変更による予期せぬ副作用を防ぐ
テストは、これらの側面を総合的に検証することで、ソフトウェアの全体的な品質を保証し、ユーザーに価値のある製品を提供することを目指します。
適切にテストスイートを整備することで、ソフトウェアの品質を向上させ、バグをすぐに検出できる
動作例の説明
テストには、品質保証以外にも重要な役割があります。それは、ソフトウェアの動作を具体的に示し、理解を深める手段としての役割です。
-
具体的なコード例を読むことで、テスト対象のユニットをより良く理解する
- テストコードは、ソフトウェアの各ユニットがどのように使用されるべきかを示す実例となる
- 開発者は、テストコードを読むことで、APIの正しい使用方法や期待される動作を理解できる
-
ソフトウェアの文章だけでは分かりにくい挙動について、具体的なコードをステップ実行することで理解を深める
- 複雑なロジックや特殊なケースの動作を、テストコードを通じて具体的に確認できる
- デバッガを使ってテストをステップ実行することで、内部の挙動を詳細に追跡し、理解を深めることができる
exampleテスト
Go言語には、exampleテストという特殊なテストがあります。
exampleテストでは、この「動作例の説明」という役割を特に重視しており、以下のような特徴があります。
-
ドキュメントとテストの統合
- exampleテストは、GoDocというドキュメント生成ツールによって自動的にドキュメントに組み込まれる
- これにより、ドキュメントとコード例が常に同期された状態を保つことができる
-
実行可能なドキュメント
- exampleテストは通常のテストと同様に実行可能である
- これにより、ドキュメントに記載されたコード例が常に最新の状態で、かつ正しく動作することが保証される
-
使用方法の明確な提示
- 関数やメソッドの使い方を、実際のコードで示すことができる
- これは、APIの使用方法を直感的に理解するのに役立つ
-
出力の検証
- exampleテストでは、期待される出力をコメントで記述する
- テスト実行時に、実際の出力がこのコメントと一致するかが確認される
例えば、以下のようなexampleテストを書くことができます:
func ExampleHello() {
fmt.Println(Hello("世界"))
// Output: こんにちは、世界さん!
}
このようなexampleテストは、コードの動作を明確に示すと同時に、その動作が正しいことを自動的に検証する手段となります。これにより、ドキュメントの正確性が保たれ、開発者がAPIをより簡単に理解し、使用できるようになります。
テストは単なる品質保証の手段だけでなく、ソフトウェアの理解を深め、適切な使用方法を示す重要な役割も果たしています。
テスト駆動開発
テスト駆動開発(TDD)は、ソフトウェア開発においてテストの役割を重視する手法です。この手法は、コードの品質向上、設計の改善、そしてドキュメンテーションの充実を同時に実現することを目指しています。
基本的な手順
TDDの核心は、以下の手順にあります。
- コードを書く前にテストを作成し、仕様を明確化する
- Red-Green-Refactorのサイクルに従って開発を進める
- Red: 失敗するテストを書く
- Green: テストをパスする最小限のコードを実装する
- Refactor: コードとテストの品質を向上させる
- テストを自動化し、継続的に実行する
この手順を踏むことで、開発者は常に検証可能なコードを書くことができ、同時に仕様との整合性を保つことができます。
実行可能ドキュメントとしてのテスト
TDDの実践において、テストコードは単なる検証ツールを超えた役割を果たします。「動くコードこそが最高のドキュメントだ」という考え方は、この手法の重要な哲学です。
テストを先に書くことで、以下のような利点が生まれます。
より良い設計の促進
テストは、実装するソフトウェアを使う側から書く必要があります。そのため、自然と利用者目線に立って設計を検証できます。
APIの使いやすさや拡張性を考慮しながらテストを書くことで、自然と良い設計が導かれます。
コミュニケーションツールとしての活用
テストコードは、開発者間で仕様や意図を共有するための効果的な手段となります。
仕様書で文章で説明するだけでは、自然言語による曖昧さによって認識がズレる可能性があります。しかし、テストコードという曖昧さの無い形式でコミュニケーションすることで、コードの問題点や設計上の選択についてより明確に議論することができるようになります。
コード理解の促進
具体的なテストケースは、APIの使用方法や期待される動作を明確に示し、コードの理解を助けます。
この点は、exampleテストの節でも説明した通りです。
品質保証との関係
TDDは品質保証に大きく貢献しますが、両者の関係には注意すべき点があります。
網羅性の不足
TDDで書かれるテストは、(テストファーストの考え方から当然のことですが)実装を見ずに作成されるため、網羅性が不足する可能性があります。
これは、TDDの実行可能ドキュメントの考え方から導き出されます。
なんらかのソフトウェアの公式文書を読むとき、次の2項目に大別されているのを見たことがあるはずです。
- ドキュメント:使い方ごとに、主要な機能や記法、APIを紹介する
- リファレンス:機能全てに対する網羅的な解説を提供する
TDDのテストは、ドキュメントの性質が強いです。そのため、リファレンスとしての性質は弱くなります。
機能について議論したいのに、主要な機能のテストがどうでもいいテストに埋もれてしまっているような状態では、ドキュメントとしての性能が下がってしまうのです。
品質保証の負荷低減
ライブラリを使うとき、大抵の場合、ドキュメントを読めばリファレンスは辞書的に参照すればよいです。
これと同じように、品質保証もたいていの場合TDDのテストで保証され、それでも保証しきれないコードパスや不足している網羅性については追加で品質保証のテストを行う必要があります。
TDDは最低限の品質を保証し、検証されていない悪いコードが開発されることを防ぐので、品質保証の負荷を低減できます。
TDDは品質保証の工程を軽減しますが、完全に置き換えるものではありません。両者の用途の違いを理解することが重要です。
品質保証と実行可能ドキュメントの役割分担
- TDDのテストは実行可能ドキュメントとして、主に設計と基本的な機能の確認に焦点を当てる
- 品質保証のテストは、より包括的で、エッジケースや異常系のテストに重点を置く
テストの用途ごとのカバレッジの役割
品質保証
カバレッジが高い場合
カバレッジは、テストの網羅度を示す指標です。そのため、カバレッジが高いテストスイートはバグの検出割合が高いと予想できます。
しかし、カバレッジが高くてもバグが完全になくなることはないです。また、カバレッジは機械的な指標のため、100%を目指すとほとんど発生しない意味のないテストデータを入力しないといけなくなり、あまり意味のあるテストができません。(特にC1、C2は難しいです)
また、C0カバレッジのような比較的達成しやすいカバレッジだと、網羅してもバグを再現するテストケースを見つけられないことが多いです。
つまり、品質保証において、カバレッジが高いことは偽陽性も偽陰性も多いので、補助的な指標であると言えます。
カバレッジが低い場合
テストの網羅性が低いことが分かります。例えば、C0が30%だと、テストが全く存在しないコードが70%なので、品質保証が不足していると言えます。つまり、品質保証において、カバレッジが低いことは自明な指標だと言えます。
品質保証自体の品質をカバレッジで保証できるか?
カバレッジが高いと低いの境界は非常にあいまいで、定量化することができません。そのため、自明な指標として自動化する場合は、「C0が50%を切る」のような基準しか使えません。現実問題、品質保証コードを真面目に書けば、その程度の簡単な目標は自動的に達成されるので、カバレッジを指標として自動化に活かすのは難しいと言えます。
カバレッジの値による自動化
警告するべきカバレッジの低さは、低い値である。また、カバレッジは粒度が大きい集計値による指標である。したがって、カバレッジの低さを通知したところでうっかりミスも防げないし、まじめに品質保証に取り組むとすぐに何の警告も出さなくなるため、意味があまりない。
動作例の説明
C0カバレッジと動作例の説明
動作例の説明では、主要な機能を説明することができればそれで良いです。また、内部の分岐についてごちゃごちゃ考える必要はありません。したがって、C0カバレッジが高いことに意味があります。
C0でカバーされてないコードは説明されていない機能です。ただし、重要ではから意図的に説明されていないのかもしれません。したがって、C0カバレッジが低いからといって、問題のあるテストスイートとは限りません。例えば、非本質的なエラー処理が多くを占める場合、C0が低くても機能を説明できる可能性があります。逆に、C0でカバーされていれば、そのコードについて何かしらの説明がされているはずです。
つまり、C0カバレッジは動作例の説明という目的において、過剰な指標としての性質が強くなります。
したがって、エディタ上でC0を達成済みのコードをハイライト表示するなど、自動通知を行うことと相性が良いです。
カバレッジの性質
ここまでで、カバレッジの性質を目的ごとに調べてきました。
ここで重要なのは、指標の性質が目的によって変わったことです。
指標は機械的に成功と失敗を判断するが、実際の品質の高さの判定基準は目的によって変化する。
この事実は、指標を使う側が指標によって踊らされるのではなく、指標の判定と実際に自分が検証したいソフトウェアの性質の間のギャップを考え、偽陽性・偽陰性の発生確率を適切に評価しなければならないことを示しています。
タイトルへの回答
この章はおまけです。
タイトルの「テスト駆動開発のテストは、品質保証のためじゃない!?」への答えは、次のようになります。
テスト駆動開発のテストは、ソフトウェアの仕様を分かりやすく示す実行可能ドキュメントであるため、テストであるにもかかわらず、品質保証が最大の目標ではない。
しかし、結果的に品質保証の仕事を大幅に減らすため、品質保証においても有意義である
おわりに
長い記事を読んでいただき、ありがとうございました。
この記事では、よく知られているカバレッジの概念と、テスト駆動開発の概念を指標という理論的観点から統一的に捉えました。個人的には、動作例と品質保証を混同して一括で管理しようとしてソフトウェア開発で苦しむことが多いと感じています。
ソフトウェアに限らず、何らかの手法を導入するときはその手法の基礎や哲学を理解してからにしたいものです。