📝

【初学者向け】単体テストや結合テストで利用される代表的なテスト手法・技法メモ

に公開

はじめに

こんにちは、ソニックムーブでフロントエンドエンジニアを担当している chiaki です!
今回は、単体テストや結合テストで利用されることの多いテスト手法・技法について自分の復習も兼ねながらまとめていきたいと思います。

ホワイトボックステスト・ブラックボックステスト

まず初めに、大きく分けてテスト手法にはホワイトボックステストとブラックボックステストというものがあります。

これらはテストの観点の違いによって分類されるもので、それぞれ異なる目的と特徴を持っています。

  • ホワイトボックステスト:コードの内部構造(実装)に着目したテスト。「どのように動いているか」を検証します
  • ブラックボックステスト:入力と出力の関係(仕様)に着目したテスト。「何ができるか」を検証します

どちらか一方だけではなく、両方を組み合わせることで、より堅牢で信頼性の高いソフトウェアを作ることができます。それぞれの手法について、詳しく見ていきましょう。

ホワイトボックステスト

ホワイトボックステストでは、網羅率(カバレッジ)と呼ばれる指標を用いて、テストの実施状況を評価します。主に以下の技法があります。

命令網羅(ステートメントテスト)

プログラム内のすべての命令文(ステートメント)を少なくとも 1 回は実行するテスト手法です。コードのすべての行が実行されることを保証しますが、条件分岐の組み合わせまでは網羅しません。最も基本的なカバレッジ基準となります。

コード例:

function calculateDiscount(price, isVIP) {
  let discount = 0;
  if (isVIP) {
    discount = price * 0.2;
  }
  return price - discount;
}

命令網羅を達成するには、すべての行が少なくとも 1 回実行されるテストケースが必要です。例えば calculateDiscount(1000, true) を実行すれば、すべての命令が実行されます。

分岐網羅(ブランチテスト)

プログラム内のすべての分岐(if 文や switch 文など)において、真(true)と偽(false)の両方のパターンを少なくとも 1 回ずつ実行するテスト手法です。命令網羅よりも厳密で、各分岐の両方の結果をテストすることで、より高い品質を保証します。

コード例:

function checkAge(age) {
  if (age >= 20) {
    return "成人です";
  } else {
    return "未成年です";
  }
}

分岐網羅を達成するには、if 文の真と偽の両方のケースをテストする必要があります。例えば checkAge(25)checkAge(15) の 2 つのテストケースが必要です。

条件網羅(コンディションテスト)

複数の条件式が組み合わさった論理式において、各条件が真と偽の両方の値を取るようにテストする手法です。例えば if (A && B) という条件があった場合、A、B それぞれが真と偽の両方のケースでテストを行います。分岐網羅よりもさらに詳細なテストが可能です。

コード例:

function canPurchase(hasAccount, hasStock) {
  if (hasAccount && hasStock) {
    return "購入可能です";
  }
  return "購入できません";
}

条件網羅を達成するには、各条件(hasAccounthasStock)が真と偽の両方を取るテストケースが必要です。例えば:

  • canPurchase(true, true) - 両方とも真
  • canPurchase(true, false) - hasAccount が真、hasStock が偽
  • canPurchase(false, true) - hasAccount が偽、hasStock が真
  • canPurchase(false, false) - 両方とも偽

ブラックボックステスト

ブラックボックステストでは、効率的にテストケースを設計するための様々な技法が用意されています。主に以下の技法があります。

同値分割

入力値の範囲を「同じ結果になる」グループ(同値クラス)に分割し、各グループから代表的な値を 1 つずつ選んでテストする手法です。テストケース数を削減しながら、効率的に網羅的なテストを行うことができます。

コード例:

function getShippingFee(amount) {
  if (amount < 3000) {
    return 500;
  } else if (amount < 10000) {
    return 300;
  } else {
    return 0;
  }
}

同値分割では、入力を以下のクラスに分けます:

  • 0 円以上 3000 円未満(例:1000 円)→ 送料 500 円
  • 3000 円以上 10000 円未満(例:5000 円)→ 送料 300 円
  • 10000 円以上(例:15000 円)→ 送料無料

各クラスから 1 つずつテストケースを選ぶことで、効率的にテストできます。

境界値分析

入力値の境界付近でバグが発生しやすいという経験則に基づき、境界値とその前後の値を重点的にテストする手法です。同値分割と組み合わせて使用されることが多く、より厳密なテストを実現します。

コード例:

function judgeGrade(score) {
  if (score >= 80) {
    return "優";
  } else if (score >= 60) {
    return "良";
  } else {
    return "不可";
  }
}

境界値分析では、境界値とその前後をテストします:

  • judgeGrade(59) → "不可"(境界の下)
  • judgeGrade(60) → "良"(境界値)
  • judgeGrade(61) → "良"(境界の上)
  • judgeGrade(79) → "良"(境界の下)
  • judgeGrade(80) → "優"(境界値)
  • judgeGrade(81) → "優"(境界の上)

デシジョンテーブル

複数の条件の組み合わせとその結果を表形式で整理し、すべての組み合わせパターンを漏れなくテストする手法です。複雑なビジネスルールや条件分岐が多い場合に有効で、テストケースの抜け漏れを防ぐことができます。

コード例:

function canBorrowBook(isMember, hasOverdueBooks, bookAvailable) {
  if (!isMember) {
    return "会員登録が必要です";
  }
  if (hasOverdueBooks) {
    return "延滞本を返却してください";
  }
  if (!bookAvailable) {
    return "貸出中です";
  }
  return "貸出可能です";
}

デシジョンテーブルで整理すると:

isMember hasOverdueBooks bookAvailable 期待される結果
false - - 会員登録が必要です
true true - 延滞本を返却してください
true false false 貸出中です
true false true 貸出可能です

(-は「どちらでも同じ結果」を意味します)

各行が 1 つのテストケースとなり、すべての重要な組み合わせを網羅できます。

終わりに

今回は、単体テストや結合テストで使われる代表的なテスト手法・技法についてまとめました。

これらのテスト技法を適切に実装することで、新機能追加やリファクタリング時のデグレを防ぎ、仕様通りの動作を保証することができます。ホワイトボックステストで内部ロジックの正確性を、ブラックボックステストでユーザー視点での動作を検証することで、多角的な品質保証が実現できます。

テストは開発の手間を増やすように感じるかもしれませんが、長期的には保守コストの削減とプロダクトの安定性向上に大きく貢献します。自分もなかなか書けていないのですが、これを機に今後の開発にもうまく取り入れていきたいと思います。

株式会社ソニックムーブ

Discussion