Panduan untuk JUnit 5

1. Ikhtisar

JUnit adalah salah satu framework pengujian unit paling populer di ekosistem Java. Versi JUnit 5 berisi sejumlah inovasi menarik, dengan tujuan untuk mendukung fitur-fitur baru di Java 8 dan yang lebih baru , serta memungkinkan berbagai gaya pengujian.

2. Ketergantungan Maven

Menyiapkan JUnit 5.x.0 cukup mudah, kita perlu menambahkan dependensi berikut ke pom.xml kita :

 org.junit.jupiter junit-jupiter-engine 5.1.0 test 

Penting untuk dicatat bahwa versi ini membutuhkan Java 8 agar dapat berfungsi .

Terlebih lagi, sekarang ada dukungan langsung untuk menjalankan pengujian Unit pada Platform JUnit di Eclipse serta IntelliJ. Anda tentu saja dapat menjalankan pengujian menggunakan tujuan Tes Maven.

Di sisi lain, IntelliJ mendukung JUnit 5 secara default. Oleh karena itu, menjalankan JUnit 5 di IntelliJ cukup sederhana, cukup klik kanan -> Jalankan, atau Ctrl-Shift-F10.

3. Arsitektur

JUnit 5 terdiri dari beberapa modul berbeda dari tiga subproyek berbeda:

3.1. Platform JUnit

Platform ini bertanggung jawab untuk meluncurkan kerangka pengujian di JVM. Ini mendefinisikan antarmuka yang stabil dan kuat antara JUnit dan kliennya seperti alat pembangunan.

Tujuan akhirnya adalah bagaimana kliennya mudah diintegrasikan dengan JUnit dalam menemukan dan menjalankan pengujian.

Ini juga mendefinisikan TestEngine API untuk mengembangkan framework pengujian yang berjalan pada platform JUnit. Dengan itu, Anda dapat memasukkan pustaka pengujian pihak ketiga, langsung ke JUnit, dengan mengimplementasikan TestEngine khusus.

3.2. JUnit Jupiter

Modul ini mencakup pemrograman baru dan model ekstensi untuk menulis tes di JUnit 5. Anotasi baru dibandingkan dengan JUnit 4 adalah:

  • @TestFactory - menunjukkan metode yang merupakan pabrik pengujian untuk pengujian dinamis
  • @DisplayName - mendefinisikan nama tampilan kustom untuk kelas pengujian atau metode pengujian
  • @Nested - menunjukkan bahwa kelas yang dianotasi adalah kelas pengujian non-statis bersarang
  • @Tag - mendeklarasikan tag untuk pengujian pemfilteran
  • @ExtendWith - digunakan untuk mendaftarkan ekstensi khusus
  • @BeforeEach - menunjukkan bahwa metode yang dianotasi akan dijalankan sebelum setiap metode pengujian (sebelumnya @Before )
  • @AfterEach - menunjukkan bahwa metode yang dianotasi akan dijalankan setelah setiap metode pengujian (sebelumnya @After )
  • @BeforeAll - menunjukkan bahwa metode yang dianotasi akan dijalankan sebelum semua metode pengujian di kelas saat ini (sebelumnya @BeforeClass )
  • @AfterAll - menunjukkan bahwa metode yang dianotasi akan dijalankan setelah semua metode pengujian di kelas saat ini (sebelumnya @AfterClass )
  • @Disable - digunakan untuk menonaktifkan metode atau class pengujian (sebelumnya @Ignore )

3.3. JUnit Vintage

Mendukung menjalankan pengujian berbasis JUnit 3 dan JUnit 4 pada platform JUnit 5.

4. Anotasi Dasar

Untuk membahas anotasi baru, kami membagi bagian tersebut menjadi beberapa grup berikut, yang bertanggung jawab untuk pelaksanaan: sebelum pengujian, selama pengujian (opsional) dan setelah pengujian:

4.1. @BeforeAll dan @BeforeEach

Di bawah ini adalah contoh kode sederhana yang akan dijalankan sebelum kasus uji utama:

@BeforeAll static void setup() { log.info("@BeforeAll - executes once before all test methods in this class"); } @BeforeEach void init() { log.info("@BeforeEach - executes before each test method in this class"); }

Penting untuk diperhatikan bahwa metode dengan anotasi @BeforeAll harus statis, jika tidak kode tidak akan dikompilasi.

4.2. @DisplayName dan @Disabled

Mari beralih ke metode opsional uji baru:

@DisplayName("Single test successful") @Test void testSingleSuccessTest() { log.info("Success"); } @Test @Disabled("Not implemented yet") void testShowSomething() { }

Seperti yang dapat kita lihat, kita dapat mengubah nama tampilan atau menonaktifkan metode dengan komentar, menggunakan anotasi baru.

4.3. @AfterEach dan @AfterAll

Terakhir, mari kita bahas metode yang terhubung ke operasi setelah eksekusi tes:

@AfterEach void tearDown() { log.info("@AfterEach - executed after each test method."); } @AfterAll static void done() { log.info("@AfterAll - executed after all test methods."); }

Harap dicatat bahwa metode dengan @AfterAll juga harus menjadi metode statis.

5. Asersi dan Asumsi

JUnit 5 mencoba memanfaatkan sepenuhnya fitur-fitur baru dari Java 8, terutama ekspresi lambda.

5.1. Pernyataan

Assertion telah dipindahkan ke org.junit.jupiter.api.Assertions dan telah ditingkatkan secara signifikan. Seperti yang disebutkan sebelumnya, Anda sekarang dapat menggunakan lambda dalam pernyataan:

@Test void lambdaExpressions() { assertTrue(Stream.of(1, 2, 3) .stream() .mapToInt(i -> i) .sum() > 5, () -> "Sum should be greater than 5"); }

Meskipun contoh di atas sepele, satu keuntungan menggunakan ekspresi lambda untuk pesan pernyataan adalah bahwa ia dievaluasi secara malas, yang dapat menghemat waktu dan sumber daya jika konstruksi pesan mahal.

It is also now possible to group assertions with assertAll() which will report any failed assertions within the group with a MultipleFailuresError:

 @Test void groupAssertions() { int[] numbers = {0, 1, 2, 3, 4}; assertAll("numbers", () -> assertEquals(numbers[0], 1), () -> assertEquals(numbers[3], 3), () -> assertEquals(numbers[4], 1) ); }

This means it is now safer to make more complex assertions, as you will be able to pinpoint the exact location of any failure.

5.2. Assumptions

Assumptions are used to run tests only if certain conditions are met. This is typically used for external conditions that are required for the test to run properly, but which are not directly related to whatever is being tested.

You can declare an assumption with assumeTrue(), assumeFalse(), and assumingThat().

@Test void trueAssumption() { assumeTrue(5 > 1); assertEquals(5 + 2, 7); } @Test void falseAssumption() { assumeFalse(5  assertEquals(2 + 2, 4) ); }

If an assumption fails, a TestAbortedException is thrown and the test is simply skipped.

Assumptions also understand lambda expressions.

6. Exception Testing

There are two ways of exception testing in JUnit 5. Both of them can be implemented by using assertThrows() method:

@Test void shouldThrowException() { Throwable exception = assertThrows(UnsupportedOperationException.class, () -> { throw new UnsupportedOperationException("Not supported"); }); assertEquals(exception.getMessage(), "Not supported"); } @Test void assertThrowsException() { String str = null; assertThrows(IllegalArgumentException.class, () -> { Integer.valueOf(str); }); }

The first example is used to verify more detail of the thrown exception and the second one just validates the type of exception.

7. Test Suites

To continue the new features of JUnit 5, we will try to get to know the concept of aggregating multiple test classes in a test suite so that we can run those together. JUnit 5 provides two annotations: @SelectPackages and @SelectClasses to create test suites.

Keep in mind that at this early stage most IDEs do not support those features.

Let's have a look at the first one:

@RunWith(JUnitPlatform.class) @SelectPackages("com.baeldung") public class AllUnitTest {}

@SelectPackage is used to specify the names of packages to be selected when running a test suite. In our example, it will run all test. The second annotation, @SelectClasses, is used to specify the classes to be selected when running a test suite:

@RunWith(JUnitPlatform.class) @SelectClasses({AssertionTest.class, AssumptionTest.class, ExceptionTest.class}) public class AllUnitTest {}

For example, above class will create a suite contains three test classes. Please note that the classes don't have to be in one single package.

8. Dynamic Tests

The last topic that we want to introduce is JUnit 5 Dynamic Tests feature, which allows to declare and run test cases generated at run-time. In contrary to the Static Tests which defines fixed number of test cases at the compile time, the Dynamic Tests allow us to define the tests case dynamically in the runtime.

Dynamic tests can be generated by a factory method annotated with @TestFactory. Let's have a look at the code example:

@TestFactory public Stream translateDynamicTestsFromStream() { return in.stream() .map(word -> DynamicTest.dynamicTest("Test translate " + word, () -> { int id = in.indexOf(word); assertEquals(out.get(id), translate(word)); }) ); }

This example is very straightforward and easy to understand. We want to translate words using two ArrayList, named in and out, respectively. The factory method must return a Stream, Collection, Iterable, or Iterator. In our case, we choose Java 8 Stream.

Please note that @TestFactory methods must not be private or static. The number of tests is dynamic, and it depends on the ArrayList size.

9. Conclusion

Artikel ini adalah ikhtisar singkat tentang perubahan yang datang dengan JUnit 5.

Kita dapat melihat bahwa JUnit 5 memiliki perubahan besar pada arsitekturnya yang terkait dengan platform launcher, integrasi dengan build tool, IDE, framework uji Unit lainnya, dll. Selain itu, JUnit 5 lebih terintegrasi dengan Java 8, terutama dengan konsep Lambdas dan Stream .

Contoh yang digunakan dalam artikel ini dapat ditemukan di proyek GitHub.