テスト戦略 レッスン6

カバレッジの正しい使い方

カバレッジ率が示すものと示さないものを理解し、数値に振り回されないテスト品質の考え方を学ぶ

カバレッジが示すもの・示さないもの

カバレッジ(coverage)とは、 テスト実行時にソースコードのどれだけの行・分岐が実際に実行されたかを示す指標です。 ただし「実行された」ことと「正しく検証された」ことは全くの別物です。

function divide(a: number, b: number): number {
  return a / b;
}

// このテストはカバレッジ上は divide() の行を100%通過するが、
// 戻り値を全く検証していないため、バグを何も検出できない
it("divide を呼び出す", () => {
  divide(10, 2); // 実行はされるが結果を確認していない
});

このテストは「カバレッジ100%」に貢献しますが、divide(10, 0)でエラーになるかもしれない不具合も、 単純な計算間違いも一切検出できません。カバレッジは通過した行の割合であって、アサーション(検証)の質までは測っていません。

「カバレッジ100%が目的化する」罠

カバレッジ率を評価指標として厳格に運用すると、数値を上げること自体が目的化してしまい、 本来の目的である「バグを防ぐ」ことから外れてしまうことがあります。

起きがちな問題

  • • アサーションのない「実行するだけ」のテストが量産される
  • • カバレッジが低い箇所を無理に削って数値を上げる
  • • 本当にリスクの高いロジックより、簡単にカバーできる行を優先してしまう

健全な使い方

  • • カバレッジは「テストされていない箇所を見つける」ための地図として使う
  • • ビジネス上重要なロジックを優先してテストする
  • • 数値目標より「変更に自信を持てるか」を判断基準にする

実装詳細ではなく振る舞いをテストする

カバレッジ稼ぎのために内部のプライベート関数を無理やり公開してテストすると、 リファクタリングのたびにテストが壊れます。公開インターフェース(外から呼べる関数)を通じて 振る舞いを検証することで、内部構造を変えてもテストは壊れにくくなります。

// 悪い例: 内部実装の詳細(プライベートなヘルパー関数)に依存したテスト
import { _internalRoundToTwoDecimals } from "./pricing"; // 本来非公開の関数

it("内部の丸め関数が正しく動く", () => {
  expect(_internalRoundToTwoDecimals(1.005)).toBe(1.01);
});

// 良い例: 公開されたインターフェース経由で振る舞いを検証する
import { calculateFinalPrice } from "./pricing";

it("税込み価格が正しく丸められて返る", () => {
  expect(calculateFinalPrice(1000, 0.1)).toBe(1100);
});

後者であれば_internalRoundToTwoDecimalsの実装方法を変えても、 外部から見た振る舞いさえ変わらなければテストは壊れません。

ポイント

  • • カバレッジは「実行された割合」であり「正しく検証された」ことは保証しない
  • • カバレッジ100%を目的化すると、意味のないテストが量産される罠がある
  • • カバレッジは「テストの抜け漏れを探す地図」として活用する
  • • 実装詳細ではなく公開インターフェース経由で振る舞いを検証する

確認クイズ

1 / 3

テストカバレッジが示すものとして正しい理解はどれか?