テスト戦略 レッスン2

単体テストの設計

1つの機能を独立して検証する単体テストの書き方と、テストしやすいコードを設計するコツを学ぶ

良い単体テストの構造: Arrange-Act-Assert

単体テストは準備(Arrange)・実行(Act)・検証(Assert)の 3ステップで構成すると読みやすくなります。1つのテストで1つの振る舞いだけを検証することも重要です。

import { describe, it, expect } from "vitest";
import { calculateShippingFee } from "./shipping";

describe("calculateShippingFee", () => {
  it("合計金額が5000円以上なら送料は0円になる", () => {
    // Arrange: 準備
    const orderTotal = 5000;

    // Act: 実行
    const fee = calculateShippingFee(orderTotal);

    // Assert: 検証
    expect(fee).toBe(0);
  });

  it("合計金額が5000円未満なら送料は500円になる", () => {
    const orderTotal = 4999;
    const fee = calculateShippingFee(orderTotal);
    expect(fee).toBe(500);
  });
});

テストしやすいコードの条件

テストが書きにくい場合、多くはテスト対象のコード自体に問題があります。 「テストしやすさ」は良い設計のバロメーターでもあります。

// before: 現在時刻・グローバル状態に依存していてテストしづらい
function isDiscountActive() {
  const now = new Date(); // テストのたびに結果が変わる
  return now.getHours() >= 20;
}

// after: 依存を引数として渡せるようにする(依存性の注入)
function isDiscountActive(currentDate: Date): boolean {
  return currentDate.getHours() >= 20;
}

// テストコード: 好きな時刻を注入できるので決定的にテストできる
it("20時以降は割引が有効になる", () => {
  const eveningDate = new Date("2026-01-01T20:00:00");
  expect(isDiscountActive(eveningDate)).toBe(true);
});

現在時刻・乱数・グローバル変数のような「隠れた入力」を関数の引数として明示することで、 テストコードから自由にコントロールできるようになります。

境界値・異常系を意識する

正常系のハッピーパスだけでなく、境界値やエラーケースをテストすることで、 本番で起きがちな「想定外」を事前に潰せます。

境界値の例

  • • 空配列・空文字列
  • • 0、負の数、最大値
  • • ちょうど閾値と等しい値

異常系の例

  • • null・undefinedが渡された場合
  • • 想定外の型が渡された場合
  • • 外部APIがエラーを返した場合

ポイント

  • • Arrange-Act-Assertの構造でテストを読みやすくする
  • • 1つのテストで1つの振る舞いだけを検証する
  • • 現在時刻・乱数などの隠れた入力は引数として明示する(依存性の注入)
  • • 正常系だけでなく境界値・異常系もテストする

確認クイズ

1 / 3

単体テストの構造として推奨される「Arrange-Act-Assert」の意味として正しいものはどれか?