一般的な 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 ステートメントやループなど、コード内で実行された分岐や決定点の割合を測定します。これにより、テストで条件文の真偽分岐の両方を調べるかどうかが決まります。

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

  1. calcCoffeeIngredient さんへの発信: coffeeName チェックマーク。 のみ
  2. calcCoffeeIngredientcoffeeNamecup チェックマーク。 に発信しています
  3. コーヒーは Espresso チェックマーク。
  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 テストのコード カバレッジを測定することは困難かつ困難です。コード カバレッジを使用する代わりに、要件カバレッジの方が適している可能性があります。これは、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({});
  });
});