一般的な 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);
}

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

コードサンプルには 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. coffeeNamecup を指定して 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({});
  });
});