はじめまして、クオリティマネージャーのヒロたです。シフトレフトの有効性は皆さんもご存じのことと思いますが、本日は単体テストについて考慮する点と良い単体テストについてご紹介します。
単体テストとは
単体テストはシステム開発におけるテスト工程の一つで、結合テストや総合テスト(システムテスト)、運用テストの前に行われるものです。つまり、テスト工程で最初に実施するテストということになります。主に開発者が行い、各関数やメソッドの動作を確認するために使用されます。一般的には、少ないコードでテストすることから不具合の原因を特定しやすく修正も容易になります。プログラミングを担当した開発者がプログラムを組んだ直後に実施できることから、その機能がどのような動作をするべきかというイメージが鮮明な状態でテストが行えるため、精度の高い検証効果が期待できます。
単体テストのメリット
- 単体テストの結果、意図した動作をしないことがわかった場合に単体テストが最小単位で行われることから、問題の個所の特定や修正がしやすい。
- 開発全体のバグ修正コストを下げる効果が高い。
- 低コストで短時間で結果が得られる。
- テストフレームワーク※1を使ってテストの自動化が可能。
※1 Java アプリケーションのJUnit、.NETアプリケーションのNUnitなど自動実行がサポートされています。
単体テストのデメリット
- テストコードを作成するのに工数がかかる
- テストコード自体は、自動化できないのでコーディング担当者が、一つひとつ作成しなくてはならない。
- テストの精度が実施者のスキルに依存する
- テスト項目の策定にはスキルが必要となる。制御フローテストでは多くのテストパターンを試す必要があるが、時間は限られているためいかに最適なパターンを作成し、組み合わせるかが重要になる。
テストカバレッジ(網羅率)とソフトウェア品質
単体テストでは網羅率という指標がよく使われます。全体のソースコードに対してどれだけがテストされたかを見る指標になります。
コード網羅率 = 実行されたコードの行数 / 総行数 (0-100%の範囲で算出)
数値化された指標は、全体を把握する上で必要ですが、数値だけに頼りすぎるとテストの質(中身)を見誤ってしまいます。
単体テストの完了条件として「C0/C1カバレッジ100%」となっているプロジェクトを見かけます。カバレッジが高ければ、ソースコードの品質が高いと言えるでしょうか。カバレッジが100%でも、テストケースが正しく作成されていなければ、バグが潜在している可能性を低くすることはできません。バグを適切に検出できるテストケースを作るにはスキルが必要です。
では、バグを適切に検出できずに潜在させてしまうテストケースとはどのようなものなのでしょうか。「Google Testing Blog: TotT: Understanding Your Coverage Data」も参考にアンチパターンをいくつか考えてみましょう。
アンチパターンその1 配列のインデックス範囲外アクセス
java
int[ ] array = new int[5];
int value = array[5]; // インデックス5は範囲外
テストケースがarray[0]からarray[4]までのインデックスをテストしている場合、C1カバレッジは100%になりますが、インデックス5にアクセスするテストケースがないと、配列の範囲外アクセスによるバグを見逃すことになります。
アンチパターンその2 null参照の処理
java
String str = null;
int length = str.length(); // null参照によるNullPointerException
strが非nullの状態でのテストケースのみを実施していると、C1カバレッジは100%になりますが、strがnullの場合のテストケースがないと、NullPointerExceptionを引き起こすバグを見逃すことになります。
アンチパターンその3 条件分岐の境界値
java
int score = 85;
if (score >= 90) {
// A評価
} else if (score >= 80) {
// B評価
}
scoreが85の場合のテストケースだけを実施していると、C1カバレッジは100%になりますが、境界値である90未満や80未満のテストケースがないと、評価のロジックに潜むバグを見逃すことになります。
これらの例は、テストケースが特定の条件や境界値に対して不十分である場合に、潜在的なバグを見逃すリスクを示しています。テスト設計においては、さまざまな入力や条件を考慮する必要があります。
良い単体テストとは
さて、良い単体テストとは、どんなものなのでしょう。Vladimir Khorikovは「Unit Testing Principles、 Practices、and Patterns」にて、次のように述べています。(「単体テストの考え方/使い方 」 2022 須田智之 訳)
良い単体テストとは、ソフトウェア開発プロジェクトを持続可能なものにすること。本書では、コードを資産ではなく、負の遺産として捉えています。プロダクションコードのコードベースに何らかの変更を加えることは、コードベースのエントロピー(無秩序の量)を増やします。コードの整理やリファクタリングをなどの適切な処置を常にしていなければ、そのソフトウェアはすぐに複雑になり秩序がなくなってしまいます。そうなると1つのバグを修正することが、より多くのバグを生み出すことになったり、他の部分が機能しなくなったり、ドミノが次々と倒れる状況に陥ります。しかし、テストを用意しておけば、それを防げます。テストがあることで、コードによる退行(regression)を検出することができるのです。
「単体テストの考え方/使い方 」 2022 須田智之 訳
本書では、良い単体テストについて以下のことが提案されています。
・プロダクションコードのリファクタリングに伴って、テストコードをリファクタリングすること
・プロダクションコードを変更するたびにテストを実施すること
・テストが間違って失敗した際にその対処をすること
・プロダクションコードの振る舞いを理解するためにテストコードを読むこと
コードが増えれば、ソフトウェアにバグが持ち込まれる経路が増えることになり、プロジェクトを維持するコストもさらに高くなる。この問題を解決するためには、コードを最小限にすべきである。テストコードも同様でバグに対して脆弱であり、保守を必要とするものである。
「単体テストの考え方/使い方 」 2022 須田智之 訳
さて、どの様にテストの質をあげればいいのでしょう。何を単体と見做すかは、それぞれのプロジェクトによって様々だと思いますが、単体テストにおける「単体」を1単位のコードでなく1単位の振る舞いにするよう推奨されています。そして、テストケースを1つずつ評価して、本当に必要なテストケースと不必要なケースを識別して、テストスイートには必要なケースのみを残して、他のテストケースは削除する作業が必要です。優れたテストスイートとは次のようなものです。
- テストが開発サイクルに組み込まれている
- コードベースの特に重要な部分のみがテスト対象になっている
- 最小限の保守コストで最大限の価値を生み出すようになっている
コードベースのすべての部分が単体テストをする価値を持っているわけではありません。ほとんどのアプリケーションにおいて核となる部分はビジネス・ロジックを含む部分です。テストに費やした時間が最も効果的な箇所に重きを置いてテストするべきです。因みに重要でないコード※2とは次の様なものです。
- インフラに関するコード
- 外部サービスや依存関係のあるもの(データベースやサードパーティーのシステム)
- 構成要素同士を結び付けるコード
※2 例に挙げたコードは、単体テストのフェーズでは、優先度は低いため重要でないコードと表現しましたが、これらの部分は統合テストや結合テスト、またはエンドツーエンド(E2E)テストなどで十分に検証する必要があります。
では、単体テストの良しあしをQA視点では、どのように見るのでしょうか。ソフトウェアの品質保証は、各テストフェーズの断面で保証する部分とトータルで保証する部分があります。単体テストでは、ひとつの振る舞いを検証しますが、システムの外部品質の保証は出来ません。少なくとも言えることは、単体テストにビジネスロジックの観点が網羅されている。間違った理由でテストに失敗することが少ない。テストに時間がかかりすぎていない。かの確認はレビューしましょう。テストフェーズは、結合テスト、統合テスト(システムテスト)、運用テストと続きます。単体テストで担保するもの、その後のテストフェーズで確認すればいいものを識別して、テストフェーズのトータルでソフトウェアの品質を高めていきましょう。
さいごに
本投稿では、単体テストについての考え方を紹介しました。カバレッジは、意味がないとまではいいませんが、たとえ単体テストのカバレッジ100%を達成したとしても、ソフトウェア品質が保証されたわけではありません。実際の品質保証にはテストの内容と深さを考慮する必要があります。皆さんがそれぞれ現場で実践してみて下さい。
最後まで読んでくださってありがとうございました。
出典
「ソフトウェア品質を高める開発者テスト」高橋寿一著 翔泳社
「単体テストの考え方/使い方 」 Vladimir Khorikov, 須田智之 マイナビ出版
「Unit Testing Principles、 Practices、and Patterns」Vladimir Khorikov (著)
参考情報
「Google Testing Blog: TotT: Understanding Your Coverage Data」