8
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

なぜテストをするのか?テストの質を向上させるためのポイント

Last updated at Posted at 2025-03-07

はじめに

ソフトウェア開発において、テストは品質を保証するために欠かせないものです。しかし、すべてのテストが良いテストとは限りません。本記事では、なぜテストを行うのか、テストの質を向上させるポイントを実例を踏まえて紹介します

テストケースの考え方や、テストが書きやすいコードについてもご紹介しているので、こちらもあわせてご覧ください。

テストが書きやすいコードを書く
テストケースの考え方

そもそもテストをなぜ行うのか?

  • バグの早期発見:開発の初期段階で問題を見つけることで、修正コストを抑える。

  • リファクタリングの安心感:既存のコードを改善する際に、動作を保証する。

  • ドキュメントとしての役割:テストコードが、関数やクラスの期待される動作を示す。

  • デプロイの安全性向上:プロダクションにリリースする前に、不具合を防ぐ。

  • 開発の効率化:自動化されたテストにより、手動での確認作業を減らせる。

テストの質を向上させるポイント

良いテストの特徴として、以下のポイントが挙げられます。

  • 実行可能性

    • 誰でもテストを実施できる。
    • 実施者に依存せず、同じ結果が得られる(再現性)。
  • 汎用性

    • テストが使い回せる(再利用可能)。
  • 冪等性

    • テストは何度実行しても同じ結果になる(結果が変わらない)。
  • KISS原則

    • シンプルかつ分かりやすく保つ。
    • 1つのテストは1つのことだけを検証する(単一責任の原則)。
  • 網羅性

    • 要件や仕様を十分にカバーし、抜け漏れがない。
    • エッジケースや異常系も含めたテストがある。
  • 独立性

    • テストケース間に依存関係がない。
    • 1つのテストが失敗しても他のテストには影響しない。

TypeScript によるテストの実例

テスト対象の関数

まず、テスト対象の calculateDiscountedPrice 関数を定義します。この関数は価格と割引率を受け取り、割引後の価格を計算します。
異常系(負の値や割引率の上限超過)についても適切にエラーハンドリングを行っています。

function calculateDiscountedPrice(price: number, discount: number): number {
  if (price < 0 || discount < 0) {
    throw new Error("Price and discount must be non-negative");
  }
  if (discount > 100) {
    throw new Error("Discount cannot exceed 100%");
  }
  return price - (price * (discount / 100));
}

推奨されるテストの特徴

  • 冪等性を満たしている
    • 同じ入力に対して常に同じ結果が得られるように設計されている。
  • 正常系・異常系のカバレッジが高い
    • 正常な入力、境界値(0% 割引, 100% 割引)、異常値(負の値, 100% 超過)などを網羅。
  • 独立したテスト
    • それぞれのテストケースが他のテストに依存せず、個別に実行可能。
describe("calculateDiscountedPrice", () => {
  it("should correctly calculate the discounted price", () => {
    expect(calculateDiscountedPrice(1000, 20)).toBe(800); // 割引適用後の正しい価格を検証
  });

  it("should return the same price when discount is 0%", () => {
    expect(calculateDiscountedPrice(500, 0)).toBe(500); // 割引がない場合
  });

  it("should return 0 when discount is 100%", () => {
    expect(calculateDiscountedPrice(1000, 100)).toBe(0); // 割引100%のケース
  });

  it("should throw an error if price is negative", () => {
    expect(() => calculateDiscountedPrice(-100, 20)).toThrow("Price and discount must be non-negative");
  });

  it("should throw an error if discount is negative", () => {
    expect(() => calculateDiscountedPrice(100, -10)).toThrow("Price and discount must be non-negative");
  });

  it("should throw an error if discount exceeds 100%", () => {
    expect(() => calculateDiscountedPrice(100, 150)).toThrow("Discount cannot exceed 100%");
  });
});

改善の余地があるテストの例

以下のテストには、いくつかの問題点があります。

  • アサーションが曖昧
    • 期待する具体的な値をチェックしていない
  • 複数のケースを1つのテストに詰め込んでいる
    • テストが失敗したときに原因が特定しづらい
  • 例外の検証が不十分
    • 正しくエラーメッセージを確認していない
describe("calculateDiscountedPrice", () => {
  it("should calculate something", () => {
    expect(calculateDiscountedPrice(1000, 20)).toBeTruthy(); // 曖昧なアサーション
  });

  it("should handle multiple cases in one test", () => {
    expect(calculateDiscountedPrice(1000, 20)).toBe(800);
    expect(calculateDiscountedPrice(200, 50)).toBe(100); // 1つのテストに複数の検証(単一責任の原則に違反)
  });

  it("should handle errors", () => {
    try {
      calculateDiscountedPrice(-100, 20);
    } catch (e) {
      expect(e).toBeInstanceOf(Error); // 具体的なエラーメッセージを確認していない
    }
  });
});

まとめ

良いテストを書くことで、バグを減らし、安心して開発ができるようになります。適切なテスト戦略を考えながら、プロジェクトに合ったテストを導入してみましょう!

8
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?