一般的な 4 種類のコード カバレッジ

コード カバレッジの概要と、4 つの一般的な測定方法について説明します。

「コード カバレッジ」という言葉をご存じですか?この投稿では、テストのコード カバレッジとは何か、そして 4 つの一般的な測定方法について説明します。

コード カバレッジとは

コード カバレッジは、テストが実行されるソースコードの割合を測定する指標です。適切なテストが行われていない部分を特定するのに役立ちます。

多くの場合、これらの指標の記録は次のようになります。

ファイル ステートメントの割合 ブランチの割合 % 関数 折れ線の割合 検出された行
file.js 90% 100% 90% 80% 89,256
coffee.js 55.55% 80% 50% 62.5% 10 ~ 11、18

新しい機能やテストを追加するほど、コード カバレッジの割合が増えると、アプリケーションが十分にテストされているという確信が高まります。しかし、これ以外にも発見すべきことはあります。

コード カバレッジの一般的な 4 つのタイプ

コード カバレッジを収集して計算するには、関数カバレッジ、行カバレッジ、分岐カバレッジ、ステートメント カバレッジという 4 つの一般的な方法があります。

テキスト カバレッジは 4 種類。

各タイプのコード カバレッジで割合がどのように計算されるかを確認するために、コーヒーの材料を計算する次のコード例を考えてみます。

/* coffee.js */

export function calcCoffeeIngredient(coffeeName, cup = 1) {
  let espresso, water;

  if (coffeeName === 'espresso') {
    espresso = 30 * cup;
    return { espresso };
  }

  if (coffeeName === 'americano') {
    espresso = 30 * cup; water = 70 * cup;
    return { espresso, water };
  }

  return {};
}

export function isValidCoffee(name) {
  return ['espresso', 'americano', 'mocha'].includes(name);
}

calcCoffeeIngredient 関数を検証するテストは次のとおりです。

/* coffee.test.js */

import { describe, expect, assert, it } from 'vitest';
import { calcCoffeeIngredient } from '../src/coffee-incomplete';

describe('Coffee', () => {
  it('should have espresso', () => {
    const result = calcCoffeeIngredient('espresso', 2);
    expect(result).to.deep.equal({ espresso: 60 });
  });

  it('should have nothing', () => {
    const result = calcCoffeeIngredient('unknown');
    expect(result).to.deep.equal({});
  });
});

こちらのライブデモでコードとテストを実行するか、リポジトリをご確認ください。

関数のカバレッジ

コード カバレッジ: 50%

/* coffee.js */

export function calcCoffeeIngredient(coffeeName, cup = 1) {
  // ...
}

function isValidCoffee(name) {
  // ...
}

関数カバレッジは単純な指標です。テストで呼び出すコード内の関数の割合を取得します。

コードサンプルには、calcCoffeeIngredientisValidCoffee の 2 つの関数があります。このテストでは calcCoffeeIngredient 関数のみを呼び出すため、関数のカバレッジは 50% です。

線のカバー範囲

コード カバレッジ: 62.5%

/* coffee.js */

export function calcCoffeeIngredient(coffeeName, cup = 1) {
  let espresso, water;

  if (coffeeName === 'espresso') {
    espresso = 30 * cup;
    return { espresso };
  }

  if (coffeeName === 'americano') {
    espresso = 30 * cup; water = 70 * cup;
    return { espresso, water };
  }

  return {};
}

export function isValidCoffee(name) {
  return ['espresso', 'americano', 'mocha'].includes(name);
}

ライン カバレッジは、テストスイートが実行した実行可能コード行の割合を測定します。コード行が未実行のままの場合は、コードの一部がテストされていないことを意味します。

コードサンプルには 8 行の実行可能コード(赤と緑でハイライト表示)がありますが、テストでは americano 条件(2 行)と isValidCoffee 関数(1 行)が実行されません。これにより、62.5% のライン カバレッジが得られます。

行カバレッジでは、function isValidCoffee(name)let espresso, water; などの宣言ステートメントは実行不可であるため、考慮されません。

支店のカバレッジ

コード カバレッジ: 80%

/* coffee.js */

export function calcCoffeeIngredient(coffeeName, cup = 1) {
  // ...

  if (coffeeName === 'espresso') {
    // ...
    return { espresso };
  }

  if (coffeeName === 'americano') {
    // ...
    return { espresso, water };
  }

  return {};
}
…

ブランチ カバレッジは、if ステートメントやループなど、コード内で実行されたブランチや決定点の割合を測定します。テストで条件文の true 分岐と false 分岐の両方を調べるかどうかを決定します。

このコードサンプルには 5 つのブランチがあります。

  1. わずか coffeeName チェックマーク。calcCoffeeIngredient に発信
  2. coffeeName さん、cup さん(チェックマーク。)で calcCoffeeIngredient に発信しています
  3. コーヒーはエスプレッソで チェックマーク。
  4. コーヒーはアメリカーノ X マーク。
  5. その他のコーヒー チェックマーク。

このテストは、Coffee is Americano 条件を除くすべてのブランチを対象としています。ブランチのカバレッジは 80%です

ステートメントの対象範囲

コード カバレッジ: 55.55%

/* coffee.js */

export function calcCoffeeIngredient(coffeeName, cup = 1) {
  let espresso, water;

  if (coffeeName === 'espresso') {
    espresso = 30 * cup;
    return { espresso };
  }

  if (coffeeName === 'americano') {
    espresso = 30 * cup; water = 70 * cup;
    return { espresso, water };
  }

  return {};
}

export function isValidCoffee(name) {
  return ['espresso', 'americano', 'mocha'].includes(name);
}

ステートメント カバレッジは、テストが実行するコード内のステートメントの割合を測定します。一見すると、「これは回線のカバレッジと同じではないでしょうか?」と疑問に思われるかもしれません。実際、ステートメント カバレッジは行 カバレッジに似ていますが、複数のステートメントを含む 1 行のコードを考慮します。

このコードサンプルでは 8 行の実行可能コードがありますが、9 つのステートメントがあります。2 つのステートメントを含む行を特定できますか?

解答を確認する

次の行が存在します。espresso = 30 * cup; water = 70 * cup;

テストでは 9 つのステートメントのうち 5 つしかカバーされていないため、ステートメントのカバレッジは 55.55% です。

常に 1 行に 1 つのステートメントを記述すると、行のカバレッジはステートメント カバレッジと類似します。

どのタイプのコード カバレッジを選択すればよいですか。

ほとんどのコード カバレッジ ツールには、これら 4 種類の一般的なコード カバレッジが含まれています。どのコード カバレッジ指標を優先するかは、特定のプロジェクト要件、開発方法、テストの目標によって決まります。

一般的に、ステートメントのカバレッジはシンプルで理解しやすい指標であるため、出発点として適しています。ステートメント カバレッジとは異なり、ブランチ カバレッジと関数カバレッジは、テストが条件(ブランチ)と関数のどちらを呼び出すかを測定します。したがって、ステートメントをカバーした後の自然な流れになります。

高いステートメント カバレッジを達成したら、ブランチ カバレッジと関数カバレッジに進むことができます。

テスト カバレッジはコード カバレッジと同じですか?

いいえ。テスト カバレッジとコード カバレッジは混同されがちですが、これらは異なります。

  • テスト カバレッジ: テストスイートがソフトウェアの機能をどの程度カバーしているかを測定する水質の指標。関連するリスクのレベルを判断するのに役立ちます。
  • コード カバレッジ: テスト中に実行されたコードの割合を測定する定量的指標。テストでカバーするコードの量が重要になります。

これは簡単にたとえて説明すると、ウェブ アプリケーションを家にたとえて考えてみましょう。

  • テスト範囲は、テストが家の中の部屋をどの程度カバーしているかを測定します。
  • コード カバレッジは、テストでどのくらいの家屋を通過したかを測定します。

コード カバレッジが 100% だからといってバグがないとは限らない

テストで高いコード カバレッジを達成することはもちろん理想的ですが、100% のコード カバレッジは、コードにバグや欠陥がないことを保証するものではありません。

100% コード カバレッジを達成する無意味な方法

次のテストについて考えてみましょう。

/* coffee.test.js */

// ...
describe('Warning: Do not do this', () => {
  it('is meaningless', () => { 
    calcCoffeeIngredient('espresso', 2);
    calcCoffeeIngredient('americano');
    calcCoffeeIngredient('unknown');
    isValidCoffee('mocha');
    expect(true).toBe(true); // not meaningful assertion
  });
});

このテストでは関数、行、ブランチ、ステートメントのカバレッジを 100% 達成していますが、実際にはコードをテストしていないため、意味はありません。expect(true).toBe(true) アサーションは、コードが正しく動作するかどうかにかかわらず、常に合格します。

指標が悪いことは指標がないより悪い

指標が低すぎると、セキュリティに関して誤った印象を与える可能性があります。指標がまったくないよりも悪い状況です。たとえば、100% のコード カバレッジを達成するテストスイートがあるのに、テストがすべて無意味である場合、コードは十分にテストされているという誤った認識をする可能性があります。アプリケーション コードの一部を誤って削除したり壊したりした場合、アプリケーションが正しく動作しなくなった場合でも、テストは合格します。

この状況を回避するには、次のようにします。

  • テストのレビュー。テストを作成してレビューし、意味があることを確認し、さまざまなシナリオでコードをテストします。
  • コード カバレッジはガイドラインとして使用し、テストの有効性やコード品質の唯一の尺度としては使用しないでください。

さまざまなタイプのテストでコード カバレッジを使用する

次の 3 種類の一般的なテストでコード カバレッジを使用する方法を詳しく見ていきましょう。

  • 単体テスト。複数の小さなシナリオとテストパスをカバーするように設計されているため、コード カバレッジの収集に最適なテストタイプです。
  • 統合テスト。統合テストのコード カバレッジを収集するのに役立ちますが、使用する際は注意が必要です。この場合、ソースコードの大部分のカバレッジを計算するため、どのテストがコードのどの部分を実際にカバーしているかを判断するのが難しいことがあります。それでも、ユニットが十分に分離されていないレガシー システムであれば、統合テストのコード カバレッジを計算することは有用です。
  • エンドツーエンド(E2E)テスト。E2E テストは複雑な性質を持つため、コード カバレッジの測定は困難で困難です。コード カバレッジを使用するよりも、要件カバレッジを使用する方が良いかもしれません。これは、E2E テストがソースコードに集中するのではなく、テストの要件を網羅することであるためです。

まとめ

コード カバレッジは、テストの有効性を測定するための有用な指標です。コード内の重要なロジックを十分にテストすることで、アプリケーションの品質を向上させることができます。

ただし、コード カバレッジは 1 つの指標にすぎないことに留意してください。テストの品質やアプリケーションの要件など、他の要素も必ず考慮してください。

100% のコード カバレッジを目指すことは目標ではありません。単体テスト、統合テスト、エンドツーエンド テスト、手動テストなど、さまざまなテスト方法を組み込んだ包括的なテスト計画とともに、コード カバレッジを使用してください。

完全なコード例と、適切なコード カバレッジによるテストを確認する。こちらのライブデモでコードとテストを実行することもできます。

/* coffee.js - a complete example */

export function calcCoffeeIngredient(coffeeName, cup = 1) {
  if (!isValidCoffee(coffeeName)) return {};

  let espresso, water;

  if (coffeeName === 'espresso') {
    espresso = 30 * cup;
    return { espresso };
  }

  if (coffeeName === 'americano') {
    espresso = 30 * cup; water = 70 * cup;
    return { espresso, water };
  }

  throw new Error (`${coffeeName} not found`);
}

function isValidCoffee(name) {
  return ['espresso', 'americano', 'mocha'].includes(name);
}
/* coffee.test.js - a complete test suite */

import { describe, expect, it } from 'vitest';
import { calcCoffeeIngredient } from '../src/coffee-complete';

describe('Coffee', () => {
  it('should have espresso', () => {
    const result = calcCoffeeIngredient('espresso', 2);
    expect(result).to.deep.equal({ espresso: 60 });
  });

  it('should have americano', () => {
    const result = calcCoffeeIngredient('americano');
    expect(result.espresso).to.equal(30);
    expect(result.water).to.equal(70);
  });

  it('should throw error', () => {
    const func = () => calcCoffeeIngredient('mocha');
    expect(func).toThrowError(new Error('mocha not found'));
  });

  it('should have nothing', () => {
    const result = calcCoffeeIngredient('unknown')
    expect(result).to.deep.equal({});
  });
});