はじめに:TestNGとは何か?
TestNG(Testing, Next Generationの略)は、Javaプログラミング言語向けに開発された、高機能なテストフレームワークです。 JUnitにインスパイアされて開発されましたが、単体テストだけでなく、より高レベルな統合テストやE2E(End-to-End)テストにも対応できる多くの強力な機能を備えています。
JUnitが各テストの独立性を重視するのに対し、TestNGはテスト間の依存関係を定義したり、テストをグループ化して実行したりする機能を標準でサポートしている点が大きな特徴です。 これにより、複雑なシナリオを持つアプリケーションのテストを、より柔軟かつ効率的に構築できます。
JUnitとの主な違い
TestNGは多くの点でJUnitと似ていますが、開発者にとって有益ないくつかの重要な違いがあります。
| 機能 | TestNG | JUnit 4/5 |
|---|---|---|
| アノテーション | @BeforeSuite, @AfterSuite, @BeforeTest, @AfterTest, @BeforeGroups, @AfterGroups など、より豊富なライフサイクルアノテーションを提供。 |
@BeforeAll, @AfterAll, @BeforeEach, @AfterEach が基本的なライフサイクル。 |
| テストの依存関係 | dependsOnMethods や dependsOnGroups 属性を用いて、テストメソッド間の実行順序を制御可能。 |
テストは原則として独立しており、実行順序に依存しない設計が推奨される(JUnit 5では @Order で順序付け可能)。 |
| グループ化 | groups 属性でテストをカテゴリ分けし、特定のグループのみを実行可能。 |
@Tag アノテーションで同様の機能を実現。 |
| パラメータ化テスト | @Parameters アノテーションと testng.xml、または @DataProvider アノテーションで柔軟に実現。 |
@ParameterizedTest と @ValueSource, @CsvSource などのソースアノテーションを組み合わせて実現。 |
| 並列実行 | testng.xml でスイート、テスト、クラス、メソッドレベルでの簡単な並列実行設定が可能。 |
JUnit 5で実験的にサポートされているが、設定はTestNGほど直感的ではない場合がある。 |
| 設定ファイル | testng.xml を使用して、テストスイートの実行を詳細に制御する。プログラムとテスト設定の分離が可能。 |
主にアノテーションベースでの設定が中心。 |
これらの特徴により、TestNGは特に大規模で複雑なテストスイートを管理する必要があるプロジェクトにおいて、その真価を発揮します。
環境構築と基本的な使い方
Maven/Gradleでのセットアップ
TestNGをプロジェクトに導入するのは非常に簡単です。MavenやGradleといったビルドツールを使用している場合、依存関係を定義ファイルに追加するだけです。
Mavenの場合 (`pom.xml`):
<dependencies>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.10.2</version> <!-- 最新バージョンを確認してください -->
<scope>test</scope>
</dependency>
</dependencies>
Gradleの場合 (`build.gradle`):
repositories {
mavenCentral()
}
dependencies {
testImplementation 'org.testng:testng:7.10.2' // 最新バージョンを確認してください
}
IDE(IntelliJ IDEAやEclipse)を使用している場合は、TestNGプラグインをインストールすると、テストの作成や実行がさらに簡単になります。
最初のテストクラス
TestNGのテストクラスは、特定のクラスを継承する必要はありません。テストしたいメソッドに @Test アノテーションを付与するだけで、そのメソッドはテストメソッドとして認識されます。
import org.testng.Assert;
import org.testng.annotations.Test;
public class SimpleCalculatorTest {
@Test
public void testAddition() {
SimpleCalculator calculator = new SimpleCalculator();
int result = calculator.add(2, 3);
// 検証: 結果が5であることを期待
Assert.assertEquals(result, 5, "加算結果が正しくありません");
}
}
// テスト対象のクラス
class SimpleCalculator {
public int add(int a, int b) {
return a + b;
}
}
この例では、`testAddition` メソッドがテストメソッドです。`Assert.assertEquals()` はTestNGが提供するアサーションメソッドで、第一引数(実際の結果)と第二引数(期待する結果)が等しいことを検証します。もし等しくなければ、テストは失敗し、第三引数のメッセージが表示されます。
主要アノテーションと実行順序
TestNGの強力さの源泉は、その豊富なアノテーションにあります。これにより、テストのセットアップやクリーンアップ処理を柔軟に制御できます。
アノテーションの種類
| アノテーション | 説明 |
|---|---|
@Test |
メソッドをテストメソッドとしてマークします。 |
@BeforeMethod |
各テストメソッド(@Test)の直前に実行されます。 |
@AfterMethod |
各テストメソッド(@Test)の直後に実行されます。 |
@BeforeClass |
現在のクラスの最初のテストメソッドが実行される前に、一度だけ実行されます。 |
@AfterClass |
現在のクラスのすべてのテストメソッドが実行された後に、一度だけ実行されます。 |
@BeforeTest |
<test> タグ(後述のtestng.xmlで定義)に含まれるすべてのテストクラスが実行される前に実行されます。 |
@AfterTest |
<test> タグに含まれるすべてのテストクラスが実行された後に実行されます。 |
@BeforeSuite |
現在のテストスイート内のすべてのテストが実行される前に、一度だけ実行されます。 |
@AfterSuite |
現在のテストスイート内のすべてのテストが実行された後に、一度だけ実行されます。 |
実行順序の階層
これらのアノテーションは、以下の階層的な順序で実行されます。これにより、テスト全体の初期化から、個々のメソッドのセットアップまで、きめ細かな制御が可能になります。
@BeforeSuite → @BeforeTest → @BeforeClass → [@BeforeMethod → @Test → @AfterMethod] → … → @AfterClass → @AfterTest → @AfterSuite
角括弧 `[]` の部分は、クラス内の各テストメソッドに対して繰り返されます。
import org.testng.annotations.*;
public class AnnotationOrderTest {
@BeforeSuite
public void beforeSuite() { System.out.println("BeforeSuite: スイート開始"); }
@BeforeTest
public void beforeTest() { System.out.println(" BeforeTest: テストタグ開始"); }
@BeforeClass
public void beforeClass() { System.out.println(" BeforeClass: クラス開始"); }
@BeforeMethod
public void beforeMethod() { System.out.println(" BeforeMethod: メソッド開始"); }
@Test
public void testMethod1() { System.out.println(" TestMethod1 実行"); }
@Test
public void testMethod2() { System.out.println(" TestMethod2 実行"); }
@AfterMethod
public void afterMethod() { System.out.println(" AfterMethod: メソッド終了"); }
@AfterClass
public void afterClass() { System.out.println(" AfterClass: クラス終了"); }
@AfterTest
public void afterTest() { System.out.println(" AfterTest: テストタグ終了"); }
@AfterSuite
public void afterSuite() { System.out.println("AfterSuite: スイート終了"); }
}
応用機能でテストを強化する
TestNGの真価は、基本的なテスト実行機能に加えて、豊富に用意された応用機能にあります。
アサーション(Assertion)
アサーションは、テストの結果が期待通りであるかを検証するための重要な仕組みです。TestNGは `org.testng.Assert` クラスを通じて、多彩なアサーションメソッドを提供します。
assertEquals(actual, expected): 2つの値が等しいことを検証します。assertNotEquals(actual, expected): 2つの値が等しくないことを検証します。assertTrue(condition): 条件がtrueであることを検証します。assertFalse(condition): 条件がfalseであることを検証します。assertNull(object): オブジェクトがnullであることを検証します。assertNotNull(object): オブジェクトがnullでないことを検証します。
また、TestNGにはソフトアサーションという概念があります。これは、アサーションが失敗しても即座にテストを中断せず、テストメソッドの最後まで実行を続ける機能です。複数の検証を一度に行いたい場合に便利です。
import org.testng.asserts.SoftAssert;
@Test
public void testWithSoftAssert() {
SoftAssert softAssert = new SoftAssert();
softAssert.assertEquals(1, 2, "最初の検証失敗"); // ここで失敗しても中断しない
System.out.println("この行は実行されます");
softAssert.assertTrue(false, "2番目の検証失敗");
// 最後にまとめて結果を報告
softAssert.assertAll(); // ここでテストが失敗としてマークされる
}
テストの無効化、タイムアウト、依存関係
@Test アノテーションの属性を使うことで、テストの振る舞いを細かく制御できます。
- 無効化 (enabled): 特定のテストを一時的に実行対象から外したい場合、`enabled = false` を設定します。
@Test(enabled = false) public void temporarilyDisabledTest() { // このテストは実行されない } - タイムアウト (timeOut): テストの実行が一定時間を超えた場合に失敗とさせたい場合、ミリ秒単位で時間を指定します。
@Test(timeOut = 1000) // 1秒以内に完了しないと失敗 public void performanceTest() throws InterruptedException { Thread.sleep(1200); // このテストはタイムアウトで失敗する } - 依存関係 (dependsOnMethods): 特定のテストが、他のテストの成功を前提とする場合に設定します。依存先のテストが失敗またはスキップされた場合、このテストも実行されずにスキップされます。
@Test public void loginTest() { // ログイン処理 Assert.assertTrue(true); } @Test(dependsOnMethods = { "loginTest" }) public void postMessageTest() { // ログインが成功している前提でのメッセージ投稿テスト }
テストの効率化:グループ化とデータ駆動テスト
テストのグループ化 (Grouping)
大規模なプロジェクトでは、テストケースが数百、数千に及ぶこともあります。TestNGのグループ化機能を使うと、関連するテストメソッドを「タグ付け」し、特定のグループに属するテストだけを選択して実行できます。これにより、「スモークテスト」「リグレッションテスト」「APIテスト」といった目的に応じたテスト実行が容易になります。
public class GroupingTest {
@Test(groups = { "smoke", "ui" })
public void testLoginUI() {
System.out.println("ログインUIテスト");
}
@Test(groups = { "regression", "api" })
public void testUserCreationAPI() {
System.out.println("ユーザー作成APIテスト");
}
@Test(groups = { "smoke", "regression", "api" })
public void testGetUserDataAPI() {
System.out.println("ユーザーデータ取得APIテスト");
}
}
これらのグループは、後述する `testng.xml` ファイルで実行対象として指定します。
データプロバイダ (Data Provider)
データ駆動テストは、同じテストロジックを異なるデータセットで繰り返し実行する手法です。TestNGでは @DataProvider アノテーションを使ってこれをエレガントに実現します。
@DataProvider を付与したメソッドは、2次元のObject配列 (Object[][]) を返す必要があります。この配列の各行が、テストメソッドの1回の実行に使うパラメータセットとなります。テストメソッド側では、dataProvider 属性でデータプロバイダメソッドの名前を指定します。
public class DataProviderTest {
// データを提供するメソッド
@DataProvider(name = "login-data")
public Object[][] loginDataProvider() {
return new Object[][] {
{ "user1", "pass1", true }, // 1回目の実行データ
{ "user2", "wrongpass", false }, // 2回目の実行データ
{ "invaliduser", "pass3", false } // 3回目の実行データ
};
}
// データを受け取ってテストを実行するメソッド
@Test(dataProvider = "login-data")
public void testLogin(String username, String password, boolean expectedResult) {
System.out.println("Testing with: " + username + "/" + password);
// ここに実際のログインロジックと検証を書く
// boolean actualResult = performLogin(username, password);
// Assert.assertEquals(actualResult, expectedResult);
}
}
この例では、`testLogin` メソッドが `login-data` プロバイダからデータを受け取り、3回実行されます。これにより、コードの重複を避けつつ、網羅的なテストが可能になります。データソースは、Excelファイルやデータベースから動的に読み込むことも可能です。
testng.xmlによる高度なテストスイート管理
testng.xml は、TestNGの心臓部とも言える設定ファイルです。このXMLファイルを使うことで、実行するテストクラス、メソッド、グループ、パラメータ、リスナーなどを一元管理し、テストスイートの実行を細かく制御できます。
基本的な構造
`testng.xml` は、<suite> をルート要素とし、その中に <test>、さらにその中に <classes> や <groups> を配置する階層構造になっています。
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd" >
<suite name="MyApplicationSuite" verbose="1" >
<test name="UserManagementTests" >
<classes>
<class name="com.example.tests.UserTest" />
<class name="com.example.tests.AdminTest" />
</classes>
</test>
</suite>
- <suite>: テストスイート全体を定義します。
name属性でスイート名を指定します。 - <test>: テストの論理的なグループを定義します。一つのスイートに複数の
<test>を含めることができます。 - <classes>: 実行するテストクラスをリストアップします。
- <class>: 完全修飾クラス名を
name属性で指定します。
グループの実行
特定のグループのみを実行、または特定のグループを除外するには <groups> タグを使用します。
<test name="SmokeTests" >
<groups>
<run>
<include name="smoke" />
<exclude name="broken" />
</run>
</groups>
<classes>
<class name="com.example.tests.GroupingTest" />
</classes>
</test>
この設定では、「smoke」グループに含まれ、かつ「broken」グループに含まれないテストが実行されます。
パラメータの引き渡し
@Parameters アノテーションと組み合わせることで、XMLファイルからテストメソッドにパラメータを渡すことができます。
<suite name="ParameterizedSuite">
<test name="BrowserTest">
<parameter name="browser" value="Chrome" />
<parameter name="url" value="https://example.com" />
<classes>
<class name="com.example.tests.BrowserLaunchTest" />
</classes>
</test>
</suite>
public class BrowserLaunchTest {
@Parameters({ "browser", "url" })
@Test
public void launchBrowser(String browser, String url) {
System.out.println("Launching " + browser + " to " + url);
// WebDriverの起動ロジックなど
}
}
並列実行
TestNGはテストの並列実行を非常に簡単に設定できます。<suite> または <test> タグの parallel 属性と thread-count 属性を使用します。
<!-- メソッドレベルで5スレッドで並列実行 -->
<suite name="ParallelSuite" parallel="methods" thread-count="5">
...
</suite>
<!-- テストタグレベルで2スレッドで並列実行 -->
<suite name="ParallelSuite" parallel="tests" thread-count="2">
<test name="Test1"> ... </test>
<test name="Test2"> ... </test>
</suite>
parallel 属性には `methods`, `tests`, `classes`, `instances` を指定でき、テストの実行時間を大幅に短縮できます。
リスナー (Listeners) で実行をカスタマイズする
リスナーは、TestNGの実行ライフサイクルにおける特定のイベント(テストの開始、成功、失敗など)を検知し、独自の処理を差し込むための仕組みです。`ITestListener` インターフェースを実装することで、テスト結果のカスタムレポーティング、失敗時のスクリーンショット取得、リトライ処理などを実現できます。
ITestListener の実装
`ITestListener` には、以下のようなメソッドが定義されています。
onTestStart(ITestResult result): テストメソッドが開始される直前に呼び出されます。onTestSuccess(ITestResult result): テストが成功したときに呼び出されます。onTestFailure(ITestResult result): テストが失敗したときに呼び出されます。onTestSkipped(ITestResult result): テストがスキップされたときに呼び出されます。
import org.testng.ITestContext;
import org.testng.ITestListener;
import org.testng.ITestResult;
public class CustomTestListener implements ITestListener {
@Override
public void onTestStart(ITestResult result) {
System.out.println("== テスト開始: " + result.getName() + " ==");
}
@Override
public void onTestSuccess(ITestResult result) {
System.out.println("== テスト成功: " + result.getName() + " ==");
}
@Override
public void onTestFailure(ITestResult result) {
System.out.println("== テスト失敗: " + result.getName() + " ==");
// ここでスクリーンショットを撮るなどの処理を実装
System.err.println(result.getThrowable());
}
// 他のメソッドも必要に応じてオーバーライド
}
リスナーの登録
作成したリスナークラスは、`testng.xml` で登録することで有効になります。
<suite name="ListenerSuite">
<listeners>
<listener class-name="com.example.listeners.CustomTestListener" />
</listeners>
<test name="MyTests">
<classes>
<class name="com.example.tests.SampleTest" />
</classes>
</test>
</suite>
または、テストクラスに `@Listeners` アノテーションを付与して登録することも可能です。
@Listeners(com.example.listeners.CustomTestListener.class)
public class SampleTest {
// ... テストメソッド ...
}
まとめ
TestNGは、単なるユニットテストフレームワークにとどまらず、複雑なテストシナリオを効率的かつ柔軟に管理するための強力な機能を数多く提供しています。アノテーションによるきめ細かな実行制御、テストのグループ化、データ駆動テスト、そして `testng.xml` によるスイート全体の管理と並列実行は、現代のソフトウェア開発におけるテストの品質と効率を飛躍的に向上させます。
本記事では、基本的な使い方から応用的な機能までを網羅的に解説しました。これらの機能を活用することで、あなたのJavaプロジェクトのテスト戦略はより洗練され、堅牢なものになるでしょう。ぜひ、実際のプロジェクトでTestNGを導入し、そのパワーを体感してください。