TestNG

テストユニットテストJavaアノテーション次世代

テストツール

TestNG

概要

TestNGは"Next Generation"の略で、JUnitとNUnitからインスピレーションを得たJavaテストフレームワークです。単体テストから統合テスト、エンドツーエンドテストまで、幅広いテストニーズを簡素化するように設計されています。豊富なアノテーション、並行実行サポート、データ駆動テスト、テスト依存関係管理など、企業レベルの複雑なテストシナリオに対応する強力な機能を提供します。

詳細

TestNGの最大の特徴は、その柔軟性と拡張性にあります。@BeforeSuite、@AfterSuite、@DataProvider、@Parameters、@Testなど豊富なアノテーションを提供し、テストの実行順序や依存関係を細かく制御できます。グループ化機能により、テストを論理的にまとめて実行でき、大規模なテストスイートでも効率的な管理が可能です。

データ駆動テストでは、CSV、Excel、データベースなどの外部データソースから動的にテストデータを読み込めます。並行実行機能により、テストメソッド、クラス、スイートレベルでの並列実行が可能で、大幅な時間短縮を実現します。また、TestNG.xmlファイルを使用してテスト設定を外部化し、実行時にテストの組み合わせを柔軟に変更できます。

メリット・デメリット

メリット

  • 豊富なアノテーション: 30以上のアノテーションでテスト制御が可能
  • 並行実行: メソッド、クラス、スイートレベルでの並列実行サポート
  • データ駆動テスト: 外部データソースからの動的テストデータ読み込み
  • 依存関係管理: テストメソッド間の依存関係を定義可能
  • グループ化: テストの論理的グループ分けと選択的実行
  • レポート機能: HTML、XMLレポートの自動生成

デメリット

  • 学習コスト: JUnitより多機能で複雑な設定が必要
  • 設定ファイル: TestNG.xmlの記述が初心者には難しい
  • Java専用: 他言語での利用は不可
  • オーバーヘッド: 小規模なプロジェクトには機能過多の場合がある

参考ページ

書き方の例

Hello World

// pom.xml
<dependency>
    <groupId>org.testng</groupId>
    <artifactId>testng</artifactId>
    <version>7.8.0</version>
    <scope>test</scope>
</dependency>

// HelloWorldTest.java
import org.testng.Assert;
import org.testng.annotations.Test;

public class HelloWorldTest {
    
    @Test
    public void testGreeting() {
        String result = greet("TestNG");
        Assert.assertEquals(result, "Hello, TestNG!");
    }
    
    private String greet(String name) {
        return "Hello, " + name + "!";
    }
}

基本テスト

import org.testng.Assert;
import org.testng.annotations.*;

public class CalculatorTest {
    private Calculator calculator;
    
    @BeforeClass
    public void setUp() {
        System.out.println("テストクラス開始");
    }
    
    @BeforeMethod
    public void beforeEachTest() {
        calculator = new Calculator();
    }
    
    @Test(priority = 1)
    public void testAddition() {
        int result = calculator.add(5, 3);
        Assert.assertEquals(result, 8, "加算が正しくありません");
    }
    
    @Test(priority = 2, dependsOnMethods = "testAddition")
    public void testSubtraction() {
        int result = calculator.subtract(10, 4);
        Assert.assertEquals(result, 6, "減算が正しくありません");
    }
    
    @Test(expectedExceptions = ArithmeticException.class)
    public void testDivisionByZero() {
        calculator.divide(10, 0);
    }
    
    @Test(timeout = 2000)
    public void testPerformance() {
        // 2秒以内に完了すべきテスト
        calculator.complexCalculation();
    }
    
    @AfterMethod
    public void afterEachTest() {
        calculator = null;
    }
    
    @AfterClass
    public void tearDown() {
        System.out.println("テストクラス終了");
    }
}

モック

import org.testng.Assert;
import org.testng.annotations.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Mockito;

public class UserServiceTest {
    
    @Mock
    private UserRepository userRepository;
    
    private UserService userService;
    
    @BeforeMethod
    public void setUp() {
        MockitoAnnotations.openMocks(this);
        userService = new UserService(userRepository);
    }
    
    @Test
    public void testFindUserById() {
        // モックの設定
        User mockUser = new User(1L, "John Doe", "[email protected]");
        Mockito.when(userRepository.findById(1L)).thenReturn(mockUser);
        
        // テスト実行
        User result = userService.findUserById(1L);
        
        // 検証
        Assert.assertNotNull(result);
        Assert.assertEquals(result.getName(), "John Doe");
        Assert.assertEquals(result.getEmail(), "[email protected]");
        
        // モックメソッドが呼ばれたことを確認
        Mockito.verify(userRepository).findById(1L);
    }
    
    @Test
    public void testCreateUser() {
        User newUser = new User(null, "Jane Smith", "[email protected]");
        User savedUser = new User(2L, "Jane Smith", "[email protected]");
        
        Mockito.when(userRepository.save(newUser)).thenReturn(savedUser);
        
        User result = userService.createUser(newUser);
        
        Assert.assertEquals(result.getId(), Long.valueOf(2L));
        Assert.assertEquals(result.getName(), "Jane Smith");
        
        Mockito.verify(userRepository).save(newUser);
    }
}

非同期テスト

import org.testng.Assert;
import org.testng.annotations.Test;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

public class AsyncOperationTest {
    
    @Test
    public void testAsyncOperation() throws Exception {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(1000); // 1秒の処理をシミュレート
                return "非同期処理完了";
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return "エラー";
            }
        });
        
        String result = future.get(2, TimeUnit.SECONDS);
        Assert.assertEquals(result, "非同期処理完了");
    }
    
    @Test(threadPoolSize = 3, invocationCount = 10)
    public void testConcurrentExecution() {
        // 3つのスレッドで10回実行される並行テスト
        long threadId = Thread.currentThread().getId();
        System.out.println("スレッドID: " + threadId + " で実行中");
        
        // 何らかの並行処理テスト
        Assert.assertTrue(true);
    }
}

セットアップ・ティアダウン

import org.testng.annotations.*;

public class DatabaseTest {
    private DatabaseConnection connection;
    private TestDataBuilder dataBuilder;
    
    @BeforeSuite
    public void globalSetUp() {
        System.out.println("=== テストスイート開始 ===");
        // データベース設定、環境設定など
    }
    
    @BeforeTest
    public void testSetUp() {
        System.out.println("テスト実行前の設定");
        connection = DatabaseConnection.getInstance();
    }
    
    @BeforeClass
    public void classSetUp() {
        System.out.println("クラスレベルのセットアップ");
        dataBuilder = new TestDataBuilder();
    }
    
    @BeforeMethod
    public void methodSetUp() {
        // 各テストメソッド実行前
        connection.beginTransaction();
        dataBuilder.createTestData();
    }
    
    @Test
    public void testUserCreation() {
        User user = new User("テストユーザー", "[email protected]");
        Long userId = connection.saveUser(user);
        
        Assert.assertNotNull(userId);
        Assert.assertTrue(userId > 0);
    }
    
    @Test
    public void testUserRetrieval() {
        Long userId = dataBuilder.getTestUserId();
        User user = connection.findUserById(userId);
        
        Assert.assertNotNull(user);
        Assert.assertEquals(user.getName(), "テストユーザー");
    }
    
    @AfterMethod
    public void methodTearDown() {
        // 各テストメソッド実行後
        connection.rollback();
        dataBuilder.cleanTestData();
    }
    
    @AfterClass
    public void classTearDown() {
        System.out.println("クラスレベルのクリーンアップ");
        dataBuilder = null;
    }
    
    @AfterTest
    public void testTearDown() {
        System.out.println("テスト実行後のクリーンアップ");
        connection.close();
    }
    
    @AfterSuite
    public void globalTearDown() {
        System.out.println("=== テストスイート終了 ===");
        // グローバルクリーンアップ
    }
}

高度な機能

import org.testng.Assert;
import org.testng.annotations.*;

public class AdvancedTestNGFeatures {
    
    // データプロバイダーを使用したデータ駆動テスト
    @DataProvider(name = "testData")
    public Object[][] createTestData() {
        return new Object[][] {
            {"apple", 5},
            {"banana", 6},
            {"cherry", 6}
        };
    }
    
    @Test(dataProvider = "testData")
    public void testStringLength(String input, int expectedLength) {
        Assert.assertEquals(input.length(), expectedLength);
    }
    
    // パラメータ化テスト
    @Parameters({"browser", "url"})
    @Test
    public void testWithParameters(String browser, String url) {
        System.out.println("ブラウザ: " + browser + ", URL: " + url);
        Assert.assertNotNull(browser);
        Assert.assertNotNull(url);
    }
    
    // グループ化テスト
    @Test(groups = {"smoke"})
    public void smokeTest1() {
        System.out.println("スモークテスト1実行");
        Assert.assertTrue(true);
    }
    
    @Test(groups = {"smoke", "regression"})
    public void smokeTest2() {
        System.out.println("スモークテスト2実行");
        Assert.assertTrue(true);
    }
    
    @Test(groups = {"regression"})
    public void regressionTest() {
        System.out.println("リグレッションテスト実行");
        Assert.assertTrue(true);
    }
    
    // グループの前後処理
    @BeforeGroups("smoke")
    public void beforeSmokeTests() {
        System.out.println("スモークテスト開始前の準備");
    }
    
    @AfterGroups("smoke")
    public void afterSmokeTests() {
        System.out.println("スモークテスト終了後のクリーンアップ");
    }
    
    // カスタムアサーション
    @Test
    public void testCustomAssertion() {
        String actual = "Hello World";
        String expected = "Hello World";
        
        // 複数のアサーションをソフトアサートで実行
        SoftAssert softAssert = new SoftAssert();
        softAssert.assertEquals(actual, expected, "文字列が一致しません");
        softAssert.assertTrue(actual.contains("Hello"), "Helloが含まれていません");
        softAssert.assertTrue(actual.contains("World"), "Worldが含まれていません");
        
        // すべてのアサーションを最後に評価
        softAssert.assertAll();
    }
}