1. 概要

JUnitとTestNGは、間違いなくJavaエコシステムで最も人気のある2つの単体テストフレームワークです。 JUnitはTestNG自体に影響を与えますが、その独特の機能を提供し、JUnitとは異なり、機能的でより高いレベルのテストで機能します。

この投稿では、これらのフレームワークの機能と一般的な使用例について説明し、比較します。

2. テスト設定

テストケースを作成している間、多くの場合、テストの実行前にいくつかの構成または初期化命令を実行し、テストの完了後にいくつかのクリーンアップを実行する必要があります。 両方のフレームワークでこれらを評価してみましょう。

JUnitは、各メソッドとクラスの前後の2つのレベルで初期化とクリーンアップを提供します。メソッドレベルと @BeforeEach @AfterEachアノテーションがあります。クラスレベルでの@BeforeAllおよび@AfterAll

public class SummationServiceTest {

    private static List<Integer> numbers;

    @BeforeAll
    public static void initialize() {
        numbers = new ArrayList<>();
    }

    @AfterAll
    public static void tearDown() {
        numbers = null;
    }

    @BeforeEach
    public void runBeforeEachTest() {
        numbers.add(1);
        numbers.add(2);
        numbers.add(3);
    }

    @AfterEach
    public void runAfterEachTest() {
        numbers.clear();
    }

    @Test
    public void givenNumbers_sumEquals_thenCorrect() {
        int sum = numbers.stream().reduce(0, Integer::sum);
        assertEquals(6, sum);
    }
}

この例ではJUnit5を使用していることに注意してください。 以前のJUnit4バージョンでは、 @前 @後と同等の注釈 @BeforeEach @AfterEach。 同じく、 @BeforeAll @結局 JUnit4の代替品です @BeforeClass @放課後。

JUnitと同様に、TestNGもメソッドおよびクラスレベルでの初期化とクリーンアップを提供します@BeforeClass@AfterClassはクラスレベルで同じままですが、メソッドレベルのアノテーションは@ BeforeMethod@AfterMethod:です。

@BeforeClass
public void initialize() {
    numbers = new ArrayList<>();
}

@AfterClass
public void tearDown() {
    numbers = null;
}

@BeforeMethod
public void runBeforeEachTest() {
    numbers.add(1);
    numbers.add(2);
    numbers.add(3);
}

@AfterMethod
public void runAfterEachTest() {
    numbers.clear();
}

TestNGは、スイートおよびグループレベルでの構成に対して、 @ BeforeSuite、@ AfterSuite、@ BeforeGroup、および@AfterGroupアノテーションも提供します。

@BeforeGroups("positive_tests")
public void runBeforeEachGroup() {
    numbers.add(1);
    numbers.add(2);
    numbers.add(3);
}

@AfterGroups("negative_tests")
public void runAfterEachGroup() {
    numbers.clear(); 
}

また、使用することができます @BeforeTest と @ AfterTest に含まれるテストケースの前後に構成が必要な場合 TestNG XML構成ファイルのタグ:

<test name="test setup">
    <classes>
        <class name="SummationServiceTest">
            <methods>
                <include name="givenNumbers_sumEquals_thenCorrect" />
            </methods>
        </class>
    </classes>
</test>

@BeforeClassおよび@AfterClassメソッドの宣言は、JUnitでは静的である必要があることに注意してください。 比較すると、TestNGメソッド宣言にはこれらの制約はありません。

3. テストを無視する

どちらのフレームワークもテストケースの無視をサポートしています。ただし、動作はまったく異なります。 JUnitは、@Ignoreアノテーションを提供します。

@Ignore
@Test
public void givenNumbers_sumEquals_thenCorrect() {
    int sum = numbers.stream().reduce(0, Integer::sum);
    Assert.assertEquals(6, sum);
}

一方、TestNGは @Test を使用し、パラメーターは「有効」で、ブール値はtrueまたはfalseです。

@Test(enabled=false)
public void givenNumbers_sumEquals_thenCorrect() {
    int sum = numbers.stream.reduce(0, Integer::sum);
    Assert.assertEquals(6, sum);
}

4. 一緒にテストを実行する

JUnit とTestNGの両方で、コレクションとして一緒にテストを実行できますが、実行方法は異なります。

@ Suite、@ SelectPackages、および@SelectClassesアノテーションを使用して、テストケースをグループ化し、JUnit5でスイートとして実行できます。スイートは、グループ化して次のように実行できるテストケースのコレクションです。単一のテスト。

異なるパッケージのテストケースをグループ化してSuite内で一緒に実行する場合は、@SelectPackagesアノテーションが必要です。

@Suite
@SelectPackages({ "org.baeldung.java.suite.childpackage1", "org.baeldung.java.suite.childpackage2" })
public class SelectPackagesSuiteUnitTest {

}

特定のテストクラスを一緒に実行したい場合は、 JUnit5@SelectClassesを通じて柔軟性を提供します。

@Suite
@SelectClasses({Class1UnitTest.class, Class2UnitTest.class})
public class SelectClassesSuiteUnitTest {

}

以前はJUnit4 を使用していましたが、@RunWithおよび@Suiteアノテーションを使用して、複数のテストをグループ化して実行しました。

@RunWith(Suite.class)
@Suite.SuiteClasses({ RegistrationTest.class, SignInTest.class })
public class SuiteTest {

}

TestNGでは、XMLファイルを使用してテストをグループ化できます。

<suite name="suite">
    <test name="test suite">
        <classes>
            <class name="com.baeldung.RegistrationTest" />
            <class name="com.baeldung.SignInTest" />
        </classes>
    </test>
</suite>

これは、RegistrationTestSignInTestが一緒に実行されることを示しています。

クラスのグループ化とは別に、TestNGは@ Test(groups =” groupName”)アノテーションを使用してメソッドをグループ化することもできます。

@Test(groups = "regression")
public void givenNegativeNumber_sumLessthanZero_thenCorrect() {
    int sum = numbers.stream().reduce(0, Integer::sum);
    Assert.assertTrue(sum < 0);
}

XMLを使用してグループを実行してみましょう。

<test name="test groups">
    <groups>
        <run>
            <include name="regression" />
        </run>
    </groups>
    <classes>
        <class 
          name="com.baeldung.SummationServiceTest" />
    </classes>
</test>

これにより、グループregressionでタグ付けされたテストメソッドが実行されます。

5. 例外のテスト

アノテーションを使用して例外をテストする機能は、JUnitとTestNGの両方で使用できます。

まず、例外をスローするメソッドを使用してクラスを作成しましょう。

public class Calculator {
    public double divide(double a, double b) {
        if (b == 0) {
            throw new DivideByZeroException("Divider cannot be equal to zero!");
        }
        return a/b;
    }
}

JUnit 5 では、 assertThrowsAPIを使用して例外をテストできます。

@Test
public void whenDividerIsZero_thenDivideByZeroExceptionIsThrown() {
    Calculator calculator = new Calculator();
    assertThrows(DivideByZeroException.class, () -> calculator.divide(10, 0));
}

JUnit 4では、テストAPIに対して @Test(expected = DivideByZeroException.class)を使用することでこれを実現できます。

また、TestNGを使用すると、同じことを実装することもできます。

@Test(expectedExceptions = ArithmeticException.class) 
public void givenNumber_whenThrowsException_thenCorrect() { 
    int i = 1 / 0;
}

この機能は、テストの一部であるコードの一部からスローされる例外を意味します。

6. パラメータ化されたテスト

パラメータ化された単体テストは、いくつかの条件下で同じコードをテストするのに役立ちます。 パラメータ化された単体テストの助けを借りて、いくつかのデータソースからデータを取得するテストメソッドを設定できます。 主なアイデアは、単体テストメソッドを再利用可能にし、別の入力セットでテストすることです。

JUnit 5では、構成されたソースから直接データ引数を消費するテストメソッドの利点があります。デフォルトでは、JUnit5は次のようないくつかのsourceアノテーションを提供します。

  • @ValueSource:タイプ Short、Byte、Int、Long、Float、Double、Char、および String:の値の配列でこれを使用できます
@ParameterizedTest
@ValueSource(strings = { "Hello", "World" })
void givenString_TestNullOrNot(String word) {
    assertNotNull(word);
}
  • @EnumSource –Enum定数をパラメーターとしてテストメソッドに渡します。
@ParameterizedTest
@EnumSource(value = PizzaDeliveryStrategy.class, names = {"EXPRESS", "NORMAL"})
void givenEnum_TestContainsOrNot(PizzaDeliveryStrategy timeUnit) {
    assertTrue(EnumSet.of(PizzaDeliveryStrategy.EXPRESS, PizzaDeliveryStrategy.NORMAL).contains(timeUnit));
}
  • @MethodSource – p ストリームを生成する外部メソッドを評価します:
static Stream<String> wordDataProvider() {
    return Stream.of("foo", "bar");
}

@ParameterizedTest
@MethodSource("wordDataProvider")
void givenMethodSource_TestInputStream(String argument) {
    assertNotNull(argument);
}
  • @CsvSource – は、パラメーターのソースとしてCSV値を使用します。
@ParameterizedTest
@CsvSource({ "1, Car", "2, House", "3, Train" })
void givenCSVSource_TestContent(int id, String word) {
	assertNotNull(id);
	assertNotNull(word);
}

同様に、クラスパスからCSVファイルを読み取る必要がある場合は @CsvFileSource 、カスタムの再利用可能なArgumentsProviderを指定するために@ArgumentSourceなどの他のソースがあります。

JUnit 4 では、テストクラスに @RunWith の注釈を付けてパラメーター化されたクラスにし、@Parameterでユニットのパラメーター値を示す注釈を付ける必要があります。テスト。

TestNGでは、@Parameterまたは@DataProviderアノテーションを使用してテストをパラメーター化できます。XMLファイルを使用している間、@ Parameter:でテストメソッドにアノテーションを付けます。

@Test
@Parameters({"value", "isEven"})
public void 
  givenNumberFromXML_ifEvenCheckOK_thenCorrect(int value, boolean isEven) {
    Assert.assertEquals(isEven, value % 2 == 0);
}

XMLファイルでデータを提供します。

<suite name="My test suite">
    <test name="numbersXML">
        <parameter name="value" value="1"/>
        <parameter name="isEven" value="false"/>
        <classes>
            <class name="baeldung.com.ParametrizedTests"/>
        </classes>
    </test>
</suite>

XMLファイルの情報を使用するのは簡単で便利ですが、場合によっては、より複雑なデータを提供する必要があります。

このために、 @DataProvider アノテーションを使用できます。これにより、テストメソッドの複雑なパラメータータイプをマッピングできます。

プリミティブデータ型に@DataProviderを使用する例を次に示します。

@DataProvider(name = "numbers")
public static Object[][] evenNumbers() {
    return new Object[][]{{1, false}, {2, true}, {4, true}};
}

@Test(dataProvider = "numbers")
public void givenNumberFromDataProvider_ifEvenCheckOK_thenCorrect
  (Integer number, boolean expected) {
    Assert.assertEquals(expected, number % 2 == 0);
}

および@DataProviderオブジェクトの場合:

@Test(dataProvider = "numbersObject")
public void givenNumberObjectFromDataProvider_ifEvenCheckOK_thenCorrect
  (EvenNumber number) {
    Assert.assertEquals(number.isEven(), number.getValue() % 2 == 0);
}

@DataProvider(name = "numbersObject")
public Object[][] parameterProvider() {
    return new Object[][]{{new EvenNumber(1, false)},
      {new EvenNumber(2, true)}, {new EvenNumber(4, true)}};
}

同様に、テスト対象の特定のオブジェクトは、データプロバイダーを使用して作成および返すことができます。 Springのようなフレームワークと統合するときに便利です。

TestNGでは、 @DataProvider メソッドは静的である必要がないため、同じテストクラスで複数のデータプロバイダーメソッドを使用できることに注意してください。

7. テストタイムアウト

テストのタイムアウトとは、特定の期間内に実行が完了しない場合、テストケースが失敗することを意味します。 JUnitとTestNGの両方がタイムアウトテストをサポートします。 JUnit 5 では、タイムアウトテストをと書くことができます。

@Test
public void givenExecution_takeMoreTime_thenFail() throws InterruptedException {
    Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(10000));
}

JUnit 4 とTestNGでは、@ Test(timeout = 1000)を使用して同じテストを実行できます。

@Test(timeOut = 1000)
public void givenExecution_takeMoreTime_thenFail() {
    while (true);
}

8. 依存テスト

TestNGは依存関係テストをサポートしています。 これは、一連のテストメソッドで、最初のテストが失敗した場合、後続のすべての依存テストがスキップされ、JUnitの場合のように失敗としてマークされないことを意味します。

電子メールを検証する必要があるシナリオを見てみましょう。成功した場合は、ログインに進みます。

@Test
public void givenEmail_ifValid_thenTrue() {
    boolean valid = email.contains("@");
    Assert.assertEquals(valid, true);
}

@Test(dependsOnMethods = {"givenEmail_ifValid_thenTrue"})
public void givenValidEmail_whenLoggedIn_thenTrue() {
    LOGGER.info("Email {} valid >> logging in", email);
}

9. テスト実行の順序

JUnit4またはTestNGでテストメソッドが実行される暗黙の順序は定義されていません。メソッドはJavaReflectionAPIによって返されるように呼び出されます。 JUnit 4以降、より決定論的ですが予測できない順序を使用します。

より詳細に制御するために、テストクラスに @FixMethodOrder アノテーションを付け、メソッドソーターについて説明します。

@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class SortedTests {

    @Test
    public void a_givenString_whenChangedtoInt_thenTrue() {
        assertTrue(
          Integer.valueOf("10") instanceof Integer);
    }

    @Test
    public void b_givenInt_whenChangedtoString_thenTrue() {
        assertTrue(
          String.valueOf(10) instanceof String);
    }

}

MethodSorters.NAME_ASCENDING パラメーターは、メソッド名が辞書式順序である順にメソッドをソートします。 このソーターとは別に、 MethodSorter.DEFAULTおよびMethodSorter.JVMも同様です。

TestNGは、テストメソッドの実行順序を制御するためのいくつかの方法も提供します。 @Testアノテーションで優先度パラメーターを提供します。

@Test(priority = 1)
public void givenString_whenChangedToInt_thenCorrect() {
    Assert.assertTrue(
      Integer.valueOf("10") instanceof Integer);
}

@Test(priority = 2)
public void givenInt_whenChangedToString_thenCorrect() {
    Assert.assertTrue(
      String.valueOf(23) instanceof String);
}

優先度は優先度に基づいてテストメソッドを呼び出しますが、次の優先度レベルを呼び出す前に1つのレベルのテストが完了することを保証するものではないことに注意してください。

TestNGで機能テストケースを作成しているときに、テストの実行ごとに実行順序を同じにする必要がある相互依存テストが発生する場合があります。 これを実現するには、前のセクションで見たように、@ TestアノテーションにdependsOnMethodsパラメーターを使用する必要があります。

10. カスタムテスト名

デフォルトでは、テストを実行するたびに、テストクラスとテストメソッド名がコンソールまたはIDEに出力されます。 JUnit 5 は、 @DisplayName アノテーションを使用して、クラスおよびテストメソッドのカスタム記述名に言及できる独自の機能を提供します。

この注釈はテストの利点を提供しませんが、技術者でない人にもテスト結果を読みやすく理解しやすくします。

@ParameterizedTest
@ValueSource(strings = { "Hello", "World" })
@DisplayName("Test Method to check that the inputs are not nullable")
void givenString_TestNullOrNot(String word) {
    assertNotNull(word);
}

テストを実行するたびに、出力にはメソッド名の代わりに表示名が表示されます。

現在、 TestNG では、カスタム名を指定する方法はありません。

11. 結論

JUnitとTestNGはどちらも、Javaエコシステムでテストするための最新のツールです。

この記事では、これら2つのテストフレームワークのそれぞれを使用してテストを作成するさまざまな方法について簡単に説明しました。

すべてのコードスニペットの実装は、TestNGおよびjunit-5Githubプロジェクトにあります。