Perbandingan JUnit vs TestNG Cepat

1. Ikhtisar

JUnit dan TestNG tidak diragukan lagi adalah dua framework pengujian unit paling populer di ekosistem Java. Meskipun JUnit menginspirasi TestNG itu sendiri, ia menyediakan fitur khasnya, dan tidak seperti JUnit, JUnit berfungsi untuk pengujian fungsional dan tingkat yang lebih tinggi.

Dalam posting ini, kami akan membahas dan membandingkan kerangka kerja ini dengan mencakup fitur dan kasus penggunaan umum mereka .

2. Pengaturan Tes

Saat menulis kasus pengujian, sering kali kita perlu menjalankan beberapa konfigurasi atau instruksi inisialisasi sebelum eksekusi pengujian, dan juga pembersihan setelah menyelesaikan pengujian. Mari kita evaluasi ini di kedua kerangka kerja.

JUnit menawarkan inisialisasi dan pembersihan pada dua level, sebelum dan sesudah setiap metode dan kelas. Kami memiliki @BeforeEach , @AfterEach penjelasan di tingkat metode dan @BeforeAll dan @AfterAll di tingkat kelas:

public class SummationServiceTest { private static List 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); } }

Perhatikan bahwa contoh ini menggunakan Junit 5. Pada sebelumnya JUnit 4 versi, kita akan perlu menggunakan @Before dan @After penjelasan yang setara dengan @BeforeEach dan @AfterEach. Demikian juga, @BeforeAll dan @AfterAll adalah pengganti untuk @BeforeClass dan @AfterClass JUnit 4 .

Mirip dengan JUnit, TestNG juga menyediakan inisialisasi dan pembersihan pada metode dan tingkat kelas . Sementara @BeforeClass dan @AfterClass tetap sama di tingkat kelas, anotasi tingkat metode adalah @ BeforeMethod dan @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 juga menawarkan anotasi @BeforeSuite, @AfterSuite, @BeforeGroup dan @AfterGroup , untuk konfigurasi pada tingkat suite dan grup:

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

Selain itu, kita dapat menggunakan @BeforeTest dan @ AfterTest jika kita memerlukan konfigurasi apa pun sebelum atau sesudah kasus uji yang disertakan dalamtag dalam file konfigurasi XML TestNG:

Perhatikan bahwa deklarasi @BeforeClass dan @AfterClass metode harus statis dalam JUnit. Sebagai perbandingan, deklarasi metode TestNG tidak memiliki batasan ini.

3. Mengabaikan Tes

Kedua framework tersebut mendukung pengabaian kasus uji , meskipun keduanya melakukannya dengan sangat berbeda. JUnit menawarkan anotasi @Ignore :

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

sementara TestNG menggunakan @Test dengan parameter "enabled" dengan nilai boolean true atau false :

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

4. Menjalankan Tes Bersama

Menjalankan pengujian bersama sebagai kumpulan dimungkinkan di JUnit dan TestNG, tetapi keduanya melakukannya dengan cara yang berbeda.

Kita bisa menggunakan anotasi @RunWith, @SelectPackages , dan @SelectClasses untuk mengelompokkan kasus pengujian dan menjalankannya sebagai rangkaian di JUnit 5 . Rangkaian adalah kumpulan kasus pengujian yang dapat kita kelompokkan bersama dan dijalankan sebagai pengujian tunggal.

Jika kita ingin mengelompokkan kasus pengujian dari paket yang berbeda untuk dijalankan bersama dalam Suite kita memerlukan anotasi @SelectPackages :

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

Jika kita ingin kelas pengujian tertentu dijalankan bersama, JUnit 5 menyediakan fleksibilitas melalui @SelectClasses :

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

Sebelumnya menggunakan JUnit 4 , kami mencapai pengelompokan dan menjalankan beberapa pengujian bersama-sama menggunakan anotasi @Suite :

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

Di TestNG kita dapat mengelompokkan pengujian dengan menggunakan file XML:

Ini menunjukkan RegistrationTest dan SignInTest akan berjalan bersama.

Selain mengelompokkan kelas, TestNG juga dapat mengelompokkan metode menggunakan anotasi @ Test (groups = "groupName") :

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

Mari gunakan XML untuk mengeksekusi grup:

Ini akan menjalankan metode pengujian yang diberi tag dengan regresi grup .

5. Pengujian Pengecualian

Fitur untuk menguji pengecualian menggunakan anotasi tersedia di JUnit dan TestNG.

Mari pertama-tama buat kelas dengan metode yang mengeluarkan pengecualian:

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; } }

Di JUnit 5 kita bisa menggunakan assertThrows API untuk menguji pengecualian:

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

Di JUnit 4, kita bisa mencapai ini dengan menggunakan @Test (diharapkan = DivideByZeroException.class) melalui API pengujian.

Dan dengan TestNG kami juga dapat menerapkan hal yang sama:

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

Fitur ini menyiratkan pengecualian apa yang dilemparkan dari sepotong kode, itu adalah bagian dari pengujian.

6. Pengujian Parameter

Pengujian unit berparameter berguna untuk menguji kode yang sama dalam beberapa kondisi. Dengan bantuan pengujian unit berparameter, kita dapat menyiapkan metode pengujian yang memperoleh data dari beberapa sumber data. Ide utamanya adalah membuat metode pengujian unit dapat digunakan kembali dan menguji dengan serangkaian input yang berbeda.

Di JUnit 5 , kami memiliki keuntungan dari metode pengujian yang menggunakan argumen data langsung dari sumber yang dikonfigurasi. Secara default, JUnit 5 menyediakan beberapa anotasi sumber seperti:

  • @ValueSource: we can use this with an array of values of type Short, Byte, Int, Long, Float, Double, Char, and String:
@ParameterizedTest @ValueSource(strings = { "Hello", "World" }) void givenString_TestNullOrNot(String word) { assertNotNull(word); }
  • @EnumSource – passes Enum constants as parameters to the test method:
@ParameterizedTest @EnumSource(value = PizzaDeliveryStrategy.class, names = {"EXPRESS", "NORMAL"}) void givenEnum_TestContainsOrNot(PizzaDeliveryStrategy timeUnit) { assertTrue(EnumSet.of(PizzaDeliveryStrategy.EXPRESS, PizzaDeliveryStrategy.NORMAL).contains(timeUnit)); }
  • @MethodSource – passes external methods generating streams:
static Stream wordDataProvider() { return Stream.of("foo", "bar"); } @ParameterizedTest @MethodSource("wordDataProvider") void givenMethodSource_TestInputStream(String argument) { assertNotNull(argument); }
  • @CsvSource – uses CSV values as a source for the parameters:
@ParameterizedTest @CsvSource({ "1, Car", "2, House", "3, Train" }) void givenCSVSource_TestContent(int id, String word) { assertNotNull(id); assertNotNull(word); }

Similarly, we have other sources like @CsvFileSource if we need to read a CSV file from classpath and @ArgumentSource to specify a custom, reusable ArgumentsProvider.

In JUnit 4, the test class has to be annotated with @RunWith to make it a parameterized class and @Parameter to use the denote the parameter values for unit test.

In TestNG, we can parametrize tests using @Parameter or @DataProvider annotations. While using the XML file annotate the test method with @Parameter:

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

and provide the data in the XML file:

While using information in the XML file is simple and useful, in some cases, you might need to provide more complex data.

For this, we can use the @DataProvider annotation which allows us to map complex parameter types for testing methods.

Here's an example of using @DataProvider for primitive data types:

@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); }

And @DataProvider for objects:

@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)}}; }

In the same way, any particular objects that are to be tested can be created and returned using data provider. It's useful when integrating with frameworks like Spring.

Notice that, in TestNG, since @DataProvider method need not be static, we can use multiple data provider methods in the same test class.

7. Test Timeout

Timed out tests means, a test case should fail if the execution is not completed within a certain specified period. Both JUnit and TestNG support timed out tests. In JUnit 5 we can write a timeout test as:

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

In JUnit 4 and TestNG we can the same test using @Test(timeout=1000)

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

8. Dependent Tests

TestNG supports dependency testing. This means in a set of test methods, if the initial test fails, then all subsequent dependent tests will be skipped, not marked as failed as in the case for JUnit.

Let's have a look at a scenario, where we need to validate email, and if it's successful, will proceed to log in:

@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. Order of Test Execution

There is no defined implicit order in which test methods will get executed in JUnit 4 or TestNG. The methods are just invoked as returned by the Java Reflection API. Since JUnit 4 it uses a more deterministic but not predictable order.

To have more control, we will annotate the test class with @FixMethodOrder annotation and mention a method sorter:

@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); } }

The MethodSorters.NAME_ASCENDING parameter sorts the methods by method name is lexicographic order. Apart from this sorter, we have MethodSorter.DEFAULT and MethodSorter.JVM as well.

While TestNG also provides a couple of ways to have control in the order of test method execution. We provide the priority parameter in the @Test annotation:

@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); }

Notice, that priority invokes test methods based on priority but does not guarantee that tests in one level are completed before invoking the next priority level.

Sometimes while writing functional test cases in TestNG, we might have an interdependent test where the order of execution must be the same for every test run. To achieve that we should use the dependsOnMethods parameter to @Test annotation as we saw in the earlier section.

10. Custom Test Name

By default, whenever we run a test, the test class and the test method name is printed in console or IDE. JUnit 5 provides a unique feature where we can mention custom descriptive names for class and test methods using @DisplayName annotation.

This annotation doesn't provide any testing benefits but it brings easy to read and understand test results for a non-technical person too:

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

Setiap kali kami menjalankan pengujian, output akan menampilkan nama tampilan, bukan nama metode.

Saat ini, di TestNG tidak ada cara untuk memberikan nama kustom.

11. Kesimpulan

Baik JUnit dan TestNG adalah alat modern untuk pengujian di ekosistem Java.

Dalam artikel ini, kami telah melihat sekilas berbagai cara menulis tes dengan masing-masing dari dua kerangka kerja tes ini.

Penerapan semua cuplikan kode dapat ditemukan di proyek TestNG dan junit-5 Github.