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