4.2

4.2 テストカバレッジとテストの有効性

概要と動機

テストカバレッジは、テストスイートによって実行されるコードの割合を測定します。行カバレッジ、分岐カバレッジ、あるいはより厳格なパスカバレッジです。これは本書全体の中で最も広く追跡される指標の一つであり、計算が安く、単一のパーセンテージとして可視化しやすく、その結果として、目標になったどんな指標についてもトピック1.2が予測するまさにその方法で、最も頻繁に操作されるものの一つです。テストスイートは、意味のあることをほとんど検証しないまま高いカバレッジを達成できます。なぜなら、カバレッジは、テストの実行中にコードが実行されたかどうかを測定するのであり、そのテストが実際にコードが正しく振る舞ったかを確認したかどうかではないからです。

カバレッジと本物のテストの有効性の間のこの隙間は、些細な脚注ではありません。それは本トピックの中心的な関心事です。関数を呼び出すがその結果について何も主張しないテストは、エッジケースにわたって関数の振る舞いを徹底的に検証するテストとまったく同じようにカバレッジを増加させます。本トピックが推奨する修正、ミューテーションテストは、コードに意図的に小さく人工的な欠陥を導入し、テストスイートが実際にそれらを捉えるかを確認するものであり、この隙間への直接の答えです。本トピックはそれを、任意の追加ではなくカバレッジの必要な補完として扱います。

大規模なチームにとって、カバレッジの目標は、しばしば組織全体で品質のゲートとして採用されますが、これはまさにトピック1.2が最も操作にさらされていると警告する、インセンティブ化された目に見える指標の一種です。対になった有効性のチェックなしに一律のカバレッジ率の要件を設定する企業や政府の組織は、事実上、本書が記述するまさにその閾値操作のパターンを奨励しています。実際の欠陥防止における対応する改善なしに、純粋に数字に到達するために書かれた些細なテストです。

重要な原則

  • カバレッジは検証ではなく実行を測定する。 あるテストによってある行が実行されたということは、そのテストがそれについて意味のある何かを確認したかについて何も語りません。
  • 有効性のチェックのないカバレッジの目標は、教科書的なグッドハートの法則の設定である(トピック1.2)。数字は改善するが、本物の品質は改善しません。
  • ミューテーションテストはカバレッジの置き換えではなく必要な補完である。 両方を一緒に使ってください。
  • カバレッジは最大化すべき目標としてよりも下限としてより有用である。 低い数字は本物にテストされていないコードを明らかにします。100%を追いかけることはしばしば逓減する、あるいは負の見返りを生み出します。
  • 重要な経路のカバレッジは、均一で一律のカバレッジよりも重要である。 すべてのコードが失敗した場合に同じリスクを負うわけではありません。

推奨事項

カバレッジを最大化すべき目標としてではなく、テストされていないコードを見つけるために使う

カバレッジの報告を、100%に向けて押し上げるべきスコアとしてではなく、主に、まったくテストされていないものの地図として扱ってください。これは本物に有用な情報です。カバレッジがゼロのコードは、閉じる価値のある本物の隙間です。カバレッジを85%から95%に押し上げることの限界的な価値は、通常はるかに低く、特にその努力が高い数字に到達するためだけの低価値のテストを生み出す場合、それにかかる労力に値しないことがよくあります。

すべてのカバレッジの目標をミューテーションテストと対にする

ミューテーションテストのツールは、比較演算子を反転させる、境界条件を変えるといった、小さな欠陥をあなたのコードに自動的に導入し、それから変異させられたそれぞれのバージョンに対してあなたのテストスイートを実行します。ほとんどの変異体を「殺す」(それに対して失敗させる)テストスイートは、本物に振る舞いを検証しています。高い行カバレッジを持ちながら低いミューテーションキル率を持つテストスイートは、意味のある確認なしにコードを実行しているだけです。この対化は、カバレッジ目標の操作に対する唯一最も効果的なガードレールであり、本書はそれを、高度な、あるいは任意の技法としてではなく標準的な慣行として推奨します。

重要な経路のカバレッジとミューテーションテストを最初に優先する

すべてのコードが等しいリスクを負うわけではありません。決済処理の経路、認証のチェック、あるいはデータ移行のスクリプトは、めったに使われない管理者向けのレポートよりもはるかに厳密なテストに値します。コードベース全体にわたって均一なカバレッジを追求するのではなく、あなたの最もリスクの高い、最も結果の重い経路を特定し、見落としとしてではなく意図的で情報を与えられたトレードオフとして本物に低リスクのコードでより低いカバレッジを受け入れながら、カバレッジとミューテーションテストの努力の両方を最初にそこに集中させてください。

特定のカバレッジ操作のパターンに注意する

カバレッジが目標になると操作される最も一般的な方法には、結果について意味のある主張を何もしないまま関数を呼び出すテスト(この指標に適用されたトピック1.2の閾値操作)、根底にある問題を修正するのではなく失敗するテストを無効化あるいは削除すること、そしてテストが難しい理由に対処するのではなくテストが難しいコードをカバレッジの計算から完全に除外することが含まれます。カバレッジの割合だけを信頼するのではなく、テストのサンプルを直接定期的に監査し、その実際の主張を読んでください。

CIパイプラインでカバレッジの天井ではなく下限を設定する

あなたのビルドパイプラインを、合意された下限を新しいコードについてカバレッジが下回ったら失敗するよう設定し、すべての変更が全体の数字をより高く押し上げることを要求するのではなく、後退を防いでください。この区別は重要です。下限は、数字をさらに少しずつ押し上げるためだけに書かれた低価値のテストを生み出す、同じ容赦ない上向きのプレッシャーを作ることなく、後退から守ります。

トレードオフ:長所と短所

アプローチ長所短所
カバレッジの割合だけ安く、単純で、ツールによって広くサポートされている容易に操作される。検証ではなく実行を測定する
カバレッジとミューテーションテストテストが実際に振る舞いを確認していることを検証し、操作に強い計算コストがより高い。ツールへの投資が必要
コードベース全体にわたる均一なカバレッジの目標述べ執行するのが単純低リスクのコードに努力を無駄にする。他の場所でリスクに比して過小投資する
リスクに基づく、重要経路優先のカバレッジ最も重要な場所に努力を集中させる本物に重要な経路を正しく特定するための判断が必要

中心にある緊張関係は単純さと誠実さです。単一のカバレッジの割合は報告しやすく目標として設定しやすいですが、その単純さこそが、それがインセンティブ化された数字になった瞬間、それをとても操作しやすくするものです。この緊張を解消するには、ミューテーションテストとリスクに基づく優先順位づけの追加された複雑さを、誠実な信号のコストとして受け入れ、重要な経路に正しく集中し強いミューテーションキル率に裏づけられた、より低い全体的なカバレッジの数字が、より高いがより均一に分布しながらより効果的に検証されていない数字よりも価値があることを、あなたのチームに明示的に伝えてください。

チームで話し合うべき問い

  1. 私たちの最もリスクの高いコード経路における私たちのミューテーションキル率はどれだけで、それは同じコードについてのカバレッジの割合とどう比較されるでしょうか。 高いカバレッジの数字と低いミューテーションキル率の間の大きな隔たりは、カバレッジだけではあなたが思っていることを教えてくれていないことの最も明確な可能な兆候です。

  2. 私たちは、何を検証すべきかについてほとんど本物の考えなしに、主にカバレッジの数字を増やすためだけにテストを書いたことがあるでしょうか。 ここでは正直であってください。これは、特にカバレッジのゲートがマージをブロックしている締め切りのプレッシャーのもとで、チームが認めたがる以上に頻繁に起こります。

  3. 私たちのカバレッジの努力は私たちの最もリスクの高いコード経路に集中しているでしょうか。それとも、そのコードが失敗した場合の結果にかかわらず均一に広がっているでしょうか。 あなたの現在のカバレッジの分布を、あなたのコードベースの正直なリスク評価と照らし合わせて地図に描き、その不一致を探してください。

  4. 私たちは、それが明らかにした根底にある問題を修正するのではなく、失敗するテストを無効化あるいは削除したことがあるでしょうか。 これはカバレッジ操作の最も有害な形態の一つです。なぜなら、報告されたカバレッジの数字がほとんど動かない間に、本物の保護を積極的に取り除くからです。

  5. 私たちのCIパイプラインは新しいコードについてカバレッジの下限を執行しているでしょうか。それとも逓減する見返りにかかわらず、ますます高い天井を押し進めているでしょうか。 あなたの現在のゲートの設計が、後退から守る正しいインセンティブを作っているか、それとも低価値のテストの水増しを報いる容赦ない上向きのプレッシャーという間違ったインセンティブを作っているかを話し合ってください。

  6. 私たちのコードベースのどのコードがカバレッジの計算から除外されており、その除外は正当化されるでしょうか、それとも本物のテストの隙間を隠しているでしょうか。 あなたの実際の除外設定をレビューしてください。このリストが、誰もそれぞれの除外がまだ正当化されるかを見直すことなく、時間とともに静かに成長するのはよくあることです。

業種別の視点

スタートアップ。 正式なカバレッジの目標はこの早い段階では通常不要です。まだ急速に変化しており、いずれにせよすぐに大幅に書き直されるかもしれないコードベースにわたって一律の割合を追求するのではなく、あなたの最もリスクが高く最もビジネスにとって重要なコード経路(通常は決済や中核のワークフローのロジック)に直接テストを書く努力を集中させてください。

中小企業。 ほとんどのCIプラットフォームは最小限のセットアップコストで自動的にカバレッジを報告します。特定の目標の割合を追いかけるのではなく、主にまったくテストされていない重要なコードを発見するためにそれを使い、それが明らかにするものに行動する能力を持ってから初めてミューテーションテストを検討してください。

企業。 一律で組織全体のカバレッジの目標は、この規模ではよくある結果の重い間違いです。なぜなら、それらは数十のチームにわたって同時に、まさに本トピックが記述する操作を奨励するからです。サービスの重要性によって異なるリスクに基づくカバレッジの期待を確立し、特に最もリスクの高いシステムのためにミューテーションテストのインフラストラクチャに投資してください。

政府。 カバレッジの要件は、時に品質保証のための鈍く簡単に指定される代理として、調達やコンプライアンスの文書に現れます。可能な場所では、契約上必要なカバレッジの割合をミューテーションテストあるいは欠陥に基づく有効性の要件と対にし、契約上のインセンティブが、本トピックが警告するまさにその低価値のテストの水増しをうっかり報いないようにしてください。

事例

企業。 あるeコマースプラットフォームの指導層は、すべての新しいコードについて全社的な95%のカバレッジ要件を設定し、厳格なCIゲートとして執行していました。2年後、本来はよくテストされているはずのコードにおける本番の欠陥の波に促された監査は、コードベースの多くにわたってミューテーションキル率が40%未満であることを発見しました。チームは、締め切りのプレッシャーのもとでゲートを満たすためだけに、振る舞いについて意味のある主張をすることなくコード経路を実行するテストを書いていたのです。この会社は一律のカバレッジ要件を、リスク階層化された方針に置き換えました。決済と認証のコードについては厳格なカバレッジに加えて80%のキル率閾値を上回る必須のミューテーションテスト、そして低リスクの内部ツールにはずっと軽いカバレッジの下限です。これは無駄になったテストの努力を減らすと同時に、本物に重要な経路における欠陥率を測定可能に改善しました。

政府。 ある公衆衛生機関の給付資格システムは、その開発ベンダー契約のもとで90%のテストカバレッジを維持することが契約上要求されていました。カバレッジ要件が満たされていたにもかかわらず出荷された重大な資格計算の欠陥の後に行われたインシデント後のレビューは、責任のある特定の関数が、有効な入力で関数を呼び出すが境界条件や無効な入力を一度もテストしなかったテストを通じて完全にそのカバレッジを達成していたこと、そしてまさにそこで欠陥が発生したことを発見しました。この機関の改訂されたベンダー契約は、資格計算コードについて、カバレッジと並んで文書化されたミューテーションテストのスコアを今では要求しており、準拠しているが効果のないテストが契約を満たすことを許していた特定の隙間を閉じています。

ビジネスケース:動機、ROI、総所有コスト

カバレッジをミューテーションテストと対にすることからの見返りは、それが本番の欠陥として代償を払う前に、見かけ上のテストの質と実際のテストの質の間の隙間を捉えることです。上記のeコマースの例はこのパターンを明確に示しています。カバレッジの要件だけが、誤った安心感を生み出し、それが最終的に、その隙間をより早く捉えたであろうミューテーションテストへの投資よりもはるかに高いコストで、欠陥の波によって暴露されたのです。

総所有コストには、ミューテーションテストの計算コストが含まれます。これは単純なカバレッジの計装よりも実行にコストがかかるため、通常はコードベース全体ではなく重要経路のコードのためにとっておかれます。加えて、結果を解釈し行動するためのエンジニアリングの時間です。そのコストは、テストの有効性における未検出の隙間のコストが最も高い、最もリスクの高いコードについて特に正当化されます。

アンチパターンと落とし穴

  • カバレッジの割合を直接の品質判決として扱う。 それは検証ではなく実行を測定します。
  • 主にカバレッジのゲートを満たすためにテストを書く。 まさにトピック1.2が警告する低価値で閾値操作のパターンを生み出します。
  • 根底にある問題を修正するのではなく、失敗するテストを無効化あるいは削除する。 報告された数字にほとんど影響を与えないまま本物の保護を取り除きます。
  • コードのリスクにかかわらず均一なカバレッジの目標を適用する。 低リスクのコードに努力を無駄にし、本物に重要な経路に過小投資します。
  • 時間とともに静かに除外リストを成長させる。 技術的には正確だが誤解を招くカバレッジの数字の背後に本物のテストの隙間を隠します。
  • カバレッジの下限ではなく天井を追求する。 本物の検証よりもテストの水増しを報いる容赦ない上向きのプレッシャーを作ります。

成熟度モデル

  • レベル1、開始: カバレッジは測定されていないか、下限、目標、あるいは有効性のチェックなしに一貫性なく測定されています。
  • レベル2、発展: カバレッジの目標は存在し追跡されていますが、ミューテーションテストやリスクに基づく優先順位づけが努力の配分に情報を与えていません。
  • レベル3、標準化: カバレッジの下限がCIで一貫して執行されており、リスクに基づく優先順位づけがカバレッジの努力がどこに集中するかを方向づけています。
  • レベル4、管理: ミューテーションテストが重要経路のコードで実行され、カバレッジと並んで満たさなければならない追跡されたキル率の閾値があり、除外リストは定期的に監査されています。
  • レベル5、最適化: 組織は、ミューテーションテストに情報を与えられた優先順位づけにたどれる具体的な欠陥の削減を指し示すことができ、カバレッジと有効性のデータが一緒になってテストへの投資の決定に直接情報を与えています。

議論のためのアイデア

  1. 私たちの唯一最も重要なコード経路における私たちのミューテーションキル率はどれだけで、私たちはそれを知っているでしょうか。
  2. 私たちは純粋にカバレッジのゲートを満たすためだけに低価値のテストを書いたことがあるでしょうか。
  3. 私たちの現在のカバレッジの努力はリスクが最も高い場所に集中しているでしょうか、それとも均一に広がっているでしょうか。
  4. 現在カバレッジの計算から除外されているコードは何で、その除外はまだ正当化されるでしょうか。
  5. 私たちの最もリスクの高いシステムへのミューテーションテストへの投資は、その計算コストに見合うでしょうか。

主な要点

  • テストカバレッジは検証ではなく実行を測定します。カバーされた行は、それが意味のある形でチェックされたかについて何も語りません。
  • テストが実際にコードを実行するだけでなく本物の欠陥を捉えることを検証するために、カバレッジをミューテーションテストと対にしてください。
  • コードベース全体にわたる均一なカバレッジを追求するのではなく、重要で高リスクな経路にテストの努力を集中させてください。
  • カバレッジを、容赦なく最大化すべき天井としてではなく、後退から守る下限として使ってください。
  • 特定のカバレッジ操作のパターンに注意してください。低価値のテスト、無効化された失敗するテスト、そして静かに成長する除外リストです。

参考文献とさらなる読書

  • Working Effectively with Legacy Code, Michael Feathers 著。
  • Jia, Yue, and Mark Harman, “An Analysis and Survey of the Development of Mutation Testing,” IEEE Transactions on Software Engineering (2011)。
  • xUnit Test Patterns, Gerard Meszaros 著。
  • Accelerate: The Science of Lean Software and DevOps, Nicole Forsgren、Jez Humble、Gene Kim 著。