1. テスト技法とは
皆さんは学校のテストは好きでしたか?
あまり好きではないという人のほうが多いかもしれませんね。
システム開発のテストもどちらかというと好きではない人のほうが多い気がします。
しかし、システム開発のテストは、実際の運用環境でのトラブルを事前に防ぐために非常に重要なステップです。システム開発のテストは学生時代のように単に成績をつけるためではなく、製品やサービスの品質を確保するためのものです。
学校のテストではスコアが低くても困るのは自分だけでした。しかし、システム開発の過程で発生するバグや不具合は、後に大きな損害や問題を引き起こすことがあります。それゆえ、しっかりとしたテストを行い、問題を早期に発見・修正することが必要となります。
また、テストを通じて開発者自身のスキルや知識も深まることが多いです。実際の問題に直面して解決することで、より実践的な経験を積むことができます。したがって、システム開発のテストを好きになることは、プロのエンジニアとしてのスキルアップの一助となるでしょう。
確かに、テスト作業は手間がかかり、繰り返しの作業も多いかもしれません。ですが、その重要性と成果を理解すれば、その価値を十分に感じることができるはずです。ちなみにテストが好きな人は「知的なパズル」としてテスト工程を捉えているようです。ご参考まで。
1.1 テスト技法の重要性
テスト技法はソフトウェア開発プロセスの重要な一部です。テストの目的は主として以下の3点です。
- エラーの早期発見のため。テストは開発プロセスの早い段階で問題を検出するための手段となります。エラーが早期に発見されると、修正のコストと時間が大幅に削減されます。
- リスクの低減のため。 テストは予期しない動作や障害が発生するリスクを低減します。テストは、システムが特定の要件を満たす確率を上げます。
- ユーザー満足度の向上のため。高品質なソフトウェアはユーザー体験を向上させ、ユーザー満足度を高めます。テストは、システムが期待通りに動作し、ユーザーのニーズを満たす確率を向上します。
1.2 テストの種類
テストは多様な手法を用いて行われ、その対象や目的により様々な種類が存在します。以下は主なテストの種類です。
- 単体テスト【Unit Test】は、コードの最小単位(関数やメソッド)が期待通りに機能することを確認します。
- 結合テスト【Integration Test】は、複数のモジュールが適切に連携して動作することを確認します。
- システムテスト【System Test】は、システム全体が正常に動作することを確認します。全体のインターフェースや統合部分に問題がないかをチェックします。
- 過負荷テスト【Load Test】は、システムが予想される最大負荷またはそれ以上を処理できるかを確認します。
- 受け入れテスト【Acceptance Test】は、システムがビジネス要件を満たすことを確認します。通常、エンドユーザーまたは顧客の視点から行われます。
- 退行テスト【Regression Test】は、新たに追加や修正が行われたコードが既存の機能に悪影響を及ぼしていないことを確認します。
動画投稿サイトを例にとってテストについて考えてみましょう。
まず、ログイン機能のテストは単体テストとして行われます。次に、ログインして動画を投稿する機能のテストは、複数の機能が連携して動作することを確認する結合テストの範疇になります。最後に、動画の投稿から視聴までのユーザーからみて意味のある一連の機能のテストはシステムテストとして実施されます。
一般的には、想定以上の人数がアクセスした場合にも性能を維持するためのテストが過負荷テストです。そして、納品前に顧客に最終チェックしてもらうのが受入テストです。
システムは機能改善や法令対応のために追加修正が行われるのが常です。その時に追加修正した以外の今まで正常に稼働していた部分に悪影響があることがあります。それをテストするのが退行テストです。
それぞれのテスト技法は、ソフトウェアの品質を確保し、信頼性を向上させる上で重要な役割を果たします。以下にそれぞれのテストの内容をもう少し詳しくお話します。
2. 単体テスト
単体テストは、ユニットテスト【unit test : UT】とも呼ばれ、個々のコードの単位(クラスやメソッドなど)が期待通りに動作することを確認します。Javaでの単体テストは、通常JUnitというフレームワークを使用して行われます。以下に、簡単な単体テストの例を示します。
まずはテスト対象のクラスを見てみましょう。以下のクラスは、整数の足し算を行うシンプルなAdder
クラスです。(コードの詳細を理解する必要はありません)
public class Adder {
public int add(int a, int b) {
return a + b;
}
}
このクラスのaddメソッドが正しく動作することを確認するためのJUnitテストは、以下のようになります。(コードの詳細を理解する必要はありません)
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
public class AdderTest {
@Test
public void testAdd() {
Adder adder = new Adder();
int result = adder.add(3, 4);
assertEquals(7, result, "3 + 4 should equal 7");
}
}
このテストコードでは、まずAdderクラスのインスタンスを作成し、次にaddメソッドを使用して3と4を加算し、その結果が7であることを期待しています。もし、addメソッドが正しく動作しなければ、このテストは失敗し、エラーメッセージとして"3 + 4 should equal 7"が表示されます。
単体テストは、コードの各部分が予期した動作をすることを確実にするための大切な役割を果たします。また、将来的にコードに変更が加えられた場合、単体テストがその変更が他の部分の動作を壊していないことを確認するための重要なツールとなります。
3. 結合テスト
結合テストは、インテグレーションテスト【integration test : IT】とも呼ばれ、異なるモジュール(部品)が互いに連携して適切に機能することを確認するためのテスト手法です。テストの対象は単一のクラスやメソッドではなく、複数のモジュールが共同で機能するシステム全体またはその一部となります。Javaを使用した結合テストの例を以下に示します。
次の2つのクラス、Adder
とMultiplier
を考えます。これらのクラスは、それぞれ整数の加算と乗算を行います。(コードの詳細を理解する必要はありません)
public class Adder {
public int add(int a, int b) {
return a + b;
}
}
public class Multiplier {
public int multiply(int a, int b) {
return a * b;
}
}
これらのクラスが独立して正しく機能することは、前述のような単体テストで確認できます。しかし、これらのクラスが連携して期待通りの結果を出力するかどうかを確認するためには、結合テストが必要となります。
次に、これらのクラスを連携させて使用するCalculatorクラスを定義します。(コードの詳細を理解する必要はありません)
public class Calculator {
private Adder adder;
private Multiplier multiplier;
public Calculator(Adder adder, Multiplier multiplier) {
this.adder = adder;
this.multiplier = multiplier;
}
public int calculate(int a, int b, int c) {
return multiplier.multiply(adder.add(a, b), c);
}
}
このCalculator
クラスのcalculate
メソッドは、Adder
クラスとMultiplier
クラスを用いて、(a + b) * c
の計算を行います。このクラスの機能をテストするためには、次のようなJUnitを使用した結合テストが考えられます。(コードの詳細を理解する必要はありません)
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
public class CalculatorTest {
@Test
public void testCalculate() {
Adder adder = new Adder();
Multiplier multiplier = new Multiplier();
Calculator calculator = new Calculator(adder, multiplier);
int result = calculator.calculate(3, 2, 4);
assertEquals(20, result, "(3 + 2) * 4 should equal 20");
}
}
この結合テストでは、Adder
、Multiplier
、そしてCalculator
クラスのインスタンスを作成し、それらが連携して期待通りの結果を出力するかを確認します。
結合テストは、異なるモジュール間のインターフェースの問題や、モジュール間でのデータの受け渡しの問題などを検出するために重要です。単体テストでは検出できないような問題を見つけることが可能となります。
4. スタブとドライバ
スタブとドライバはソフトウェアテストにおける特定のテスト手法で使用されるプログラムです。これらは主に結合テストという特定のテストレベルに関連して使用されます。
結合テストは、個々のモジュールが統合されて全体のシステムとして正常に動作することを確認するためのテストです。しかし、全てのモジュールがまだ完成していない場合、テストを進めることが難しいです。完成していないモジュールの代わりにスタブとドライバが使用されます。
スタブ【Stub】
スタブは、まだ実装されていないあるいは利用できないモジュールを代替するために作成される簡単なプログラムまたは関数です。スタブは、特定の関数呼び出しをシミュレートし、ダミーの結果を返します。テスト対象のモジュールが他のモジュールと連携して正常に動作するかを確認することができます。
ドライバ【Driver】
ドライバは、テスト対象のモジュールを呼び出すために使用される簡単なプログラムまたは関数です。ドライバは、テスト対象のモジュールが他のモジュールを正しく呼び出して、その結果を適切に処理できるかを検証するのに役立ちます。
具体的なテストフローにおいて、スタブとドライバは以下のように使用されます。
ボトムアップ結合テストの場合
- 下位のモジュール(まだ完成していない)のテストを行うために、スタブが上位のモジュールの代わりに使用されます。
- 上位のモジュールがテストされる際に、下位のモジュールを呼び出すためにドライバが使用されます。
トップダウン結合テストの場合
- 上位のモジュールをテストするために、下位のモジュールの代わりにスタブが使用されます。
- 下位のモジュールがテストされる際に、上位のモジュールを呼び出すためにドライバが使用されます。
スタブとドライバは、モジュール間のインターフェースをシミュレートすることにより、結合テストを進めるための便利な手段となります。個々のモジュールが完成する前に結合テストを並行して進めることができます。
スタブとドライバの例
仮想的な計算機のプログラムを想定して、加算機能をテストする場面で、スタブとドライバを導入する例を説明します。
計算機のプログラム(コードの詳細を理解する必要はありません)
public class Calculator {
public int add(int a, int b) {
return a + b;
}
}
テストケースを実行するためのドライバ(コードの詳細を理解する必要はありません)
public class TestDriver {
public static void main(String[] args) {
// ドライバを使用してテストを実行する例
// テスト対象のオブジェクトを作成
Calculator calculator = new Calculator();
// テストケース1: 5 + 3 = 8 を期待する
int result1 = calculator.add(5, 3);
System.out.println("Test Case 1 - Result: " + result1);
// テストケース2: -2 + 7 = 5 を期待する
int result2 = calculator.add(-2, 7);
System.out.println("Test Case 2 - Result: " + result2);
// テストケース3: 0 + 0 = 0 を期待する
int result3 = calculator.add(0, 0);
System.out.println("Test Case 3 - Result: " + result3);
}
}
まだ実装されていない部分を代替するためのスタブ(コードの詳細を理解する必要はありません)
public class StubCalculator extends Calculator {
@Override
public int add(int a, int b) {
// スタブの場合、ダミーの戻り値を返す
return 999; // 仮のダミー値
}
}
上記のスタブ StubCalculator
は、Calculator
クラスの add
メソッドをオーバーライドして、固定の値 999
を返すダミーの実装です。
スタブを使用する場合のテスト(コードの詳細を理解する必要はありません)
public class TestWithStub {
public static void main(String[] args) {
// スタブを使用してテストを実行する例
// テスト対象のオブジェクトをスタブに差し替え
Calculator calculator = new StubCalculator();
// テストケース1: 5 + 3 の代わりに 999 を返す
int result1 = calculator.add(5, 3);
System.out.println("Test Case 1 - Result: " + result1);
// テストケース2: -2 + 7 の代わりに 999 を返す
int result2 = calculator.add(-2, 7);
System.out.println("Test Case 2 - Result: " + result2);
// テストケース3: 0 + 0 の代わりに 999 を返す
int result3 = calculator.add(0, 0);
System.out.println("Test Case 3 - Result: " + result3);
}
}
5. システムテスト
システムテストは、総合テストとも呼ばれ、すべてのモジュールが統合された完全なシステムが正しく機能するかを確認するテスト手法です。システム全体がビジネス要件を満たしているか、全体的に期待通りに動作するかを評価します。
具体的なシステムテストの例として、Webアプリケーションのテストを考えてみましょう。Webアプリケーションは通常、フロントエンドとバックエンドから成り立ちます。フロントエンドはユーザーインターフェースを提供し、バックエンドはデータの処理と保存を担当します。これらのモジュールはそれぞれユニットテストと結合テストを通じてテストされますが、それらが全体としてうまく機能するかを確認するためにはシステムテストが必要です。
例えば、ユーザーがウェブサイトにログインして、アカウントの詳細を表示し、更新するという一連の操作を通じてシステムが正常に動作するかを確認するのがシステムテストの例です。システムテストは、全体的なユーザーエクスペリエンスを確認することが重要で、通常は手動テストまたは自動化テストツール(例えば、Selenium)を使用します。
以下に、SeleniumとJUnitを使用したシステムテストのサンプルコードを示します。(コードの詳細を理解する必要はありません)
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.By;
import org.openqa.selenium.chrome.ChromeDriver;
public class SystemTest {
@Test
public void testLoginAndAccountUpdate() {
WebDriver driver = new ChromeDriver();
// Navigate to the login page
driver.get("http://www.example.com/login");
// Input username and password, and submit the form
WebElement username = driver.findElement(By.name("username"));
WebElement password = driver.findElement(By.name("password"));
username.sendKeys("testuser");
password.sendKeys("testpassword");
password.submit();
// Navigate to the account details page
driver.get("http://www.example.com/account-details");
// Check the account details
WebElement accountName = driver.findElement(By.id("account-name"));
assertEquals("Test User", accountName.getText());
// Close the browser
driver.quit();
}
}
このテストコードは、ユーザーがログインページにアクセスし、ログイン情報を入力してログインし、アカウント詳細ページに移動し、そのページに表示されているアカウント名が期待通りであることを確認するシステムテストを表しています。
このようなシステムテストにより、個々の部分が正しく機能していても全体としてシステムが適切に動作しないという問題を見つけることができます。
6. その他のテスト
上記以外にも様々なテスト技法があります。
6.1 過負荷テスト【Load Test】
過負荷テストは、システムが予想される最大負荷またはそれ以上の負荷を処理できるかを確認するためのテストです。システムのパフォーマンスがどの程度までスケールし、何人のユーザーをサポートできるかを評価できます。
過負荷テストは、一般的には専用のパフォーマンステストツールを使用して実施されます。これらのツールは、システムに対して大量のリクエストを発行し、システムがその負荷をどの程度まで処理できるかを評価します。
6.2 受け入れテスト【Acceptance Test】
受け入れテストは、システムがビジネス要件を満たすかどうかを確認するためのテストです。具体的には、エンドユーザーの視点でシステム全体の動作をテストします。これは、システムが実際の運用環境で適切に機能し、ユーザーが必要とするタスクを達成できることを確認します。
受け入れテストは通常、ユーザーストーリーまたはビジネス要件に基づいて設計され、システムが適切に機能するかどうかをユーザーの視点から評価します。通常はユーザーによって手動でテストされます。
6.3 退行テスト【Regression Test】
退行テストは、新しい機能を追加したり、既存のコードを修正したりした後に、システムの他の部分が依然として正常に動作することを確認するためのテストです。つまり、新しいコードが既存の機能に"退行"(不具合を引き起こす)していないことを検証します。
退行テストは特に大規模なソフトウェアプロジェクトで重要となります。
7. ホワイトボックステストとブラックボックステスト
ホワイトボックステストとブラックボックステストは、ソフトウェアテストにおける異なるアプローチを表す用語です。それぞれの特徴や適用されるシナリオを解説します。
7.1 ホワイトボックステスト【White Box Testing】
ホワイトボックステストは、ソフトウェアの内部構造やコードの実装に着目してテストを行う手法です。開発者やテスターはソフトウェアのソースコードや内部ロジックを知っている必要があります。テストケースは、プログラムの特定の部分(条件分岐、ループ、関数など)をテストすることを目的として作成されます。主な特徴は以下のとおりです。
- テスト対象のソフトウェアの内部構造に精通している必要があるため、開発者によって実施されることが多い。
- コードの網羅率(カバレッジ)を高めることが可能で、特定のパスや条件をテストすることができる。
- プログラムの欠陥やロジックエラーを特定するのに効果的である。
- ホワイトボックステストは、単体テストや結合テストのレベルでよく使用される。
代表的な手法には、条件網羅テスト(条件分岐の真偽値をすべてテストする)、状態遷移テスト(状態遷移をテストする)、制御フローテスト(コードの制御フローをテストする)などがあります。
7.2 ブラックボックステスト 【Black Box Testing】
ブラックボックステストは、ソフトウェアの内部構造や実装の詳細について知識を持たずに、外部の入力と出力をテストする手法です。テスターはソフトウェアを「ブラックボックス:中身の見えない箱」として捉え、主に仕様書や要件に基づいてテストケースを作成します。主な特徴は以下のとおりです。
- テストを実施するために、開発者はソフトウェアの内部構造を知る必要がないため、開発者でなくてもテストを行うことができる。
- ソフトウェアの外部からの振る舞いをテストするため、ユーザビリティや機能要件を確認するのに適している。
- ソフトウェアの内部の実装の変更に対しても影響を受けず、柔軟にテストを実施できる。
- ブラックボックステストは、システムテストや受け入れテストなどのレベルで広く使用される。
代表的な手法には、等価クラステスト(入力値の代表となるクラスをテストする)、境界値テスト(範囲の端や境界をテストする)、ペアワイズテスト(組み合わせテスト)などがあります。
ホワイトボックステストとブラックボックステストは、互いに補完的なテスト手法として使われることがあります。テスト全体のカバレッジと品質向上のために、両方の手法を組み合わせてテストを実施することが良い結果をもたらす場合が多いです。
8. テスト計画とテストケースの作成
8.1 テスト計画の重要性
テスト計画はテスト活動の全体像を提供し、テストの目的、範囲、方法を明確にします。以下はテスト計画を立てるメリットです。
- テスト計画により、チームはテストの目的と範囲を理解し、それぞれの役割と責任を明確にすることができます。
- テスト計画はテスト活動に必要なリソース(時間、人員、ツール等)を明確にし、それらのリソースを最も効率的な方法で使用する計画を立てることを可能にします。
- テスト計画は、潜在的なリスクを評価し、これらのリスクを管理する方法を策定します。
- 全てのテスト活動が計画に基づいて行われることで、テストの一貫性と信頼性が向上します。
8.2 テストケースの作成方法
テストケースは、システムが特定の要件を満たしているかを検証するための具体的なシナリオです。テストケースを作成する基本的なステップは以下のとおりです。
- テストケースの作成は、システムの要件や機能を理解することから始まります。この理解は、テストケースが正確かつ包括的であることを確実にします。
- 要件を理解したら、それぞれに対するテスト条件を識別します。テスト条件は、テストの対象となる特定の機能やシナリオを表します。
- テスト条件を基にしてテストケースを設計します。各テストケースには、テスト手順、入力データ、期待結果、実際の結果を記載します。
- テストケースは他のチームメンバーやステークホルダーによってレビューされ、必要に応じて調整されます。テストケースが要件を正確に反映していることを確認します。
8.3 テストケースの例
以下に、単純なログイン機能に対するテストケースの例を示します。
テストケース1(正常系)
テストケースID: TC001
- テスト条件: 正しいユーザー名とパスワードを入力する
- テスト手順: ユーザー名とパスワードを入力し、ログインボタンをクリックする
- 入力データ: ユーザー名: "user1", パスワード: "password1"
- 期待結果: ユーザーは正常にログインし、ホームページにリダイレクトされる
- 実際の結果: (テスト実行後に記入)
テストケース2(異常系)
テストケースID: TC002
- テスト条件: 不正なパスワードを入力する
- テスト手順: ユーザー名とパスワードを入力し、ログインボタンをクリックする
- 入力データ: ユーザー名: "user1", パスワード: "wrongpassword"
- 期待結果: ユーザーはログインに失敗し、エラーメッセージが表示される
- 実際の結果: (テスト実行後に記入)
これらのテストケースを使用して、ログイン機能が正しく動作するかどうかを検証できます。
次回は、「セキュリティと暗号化を知り安全性を高める」を学びます。