JUnit 5 adalah standar de facto baru untuk mengembangkan pengujian unit di Java. Versi terbaru ini telah meninggalkan batasan Java 5 dan mengintegrasikan banyak fitur dari Java 8, terutama dukungan untuk ekspresi lambda.
Di paruh pertama dari pengantar dua bagian JUnit 5 ini, Anda akan memulai pengujian dengan JUnit 5. Saya akan menunjukkan kepada Anda cara mengkonfigurasi proyek Maven untuk menggunakan JUnit 5, cara menulis pengujian menggunakan anotasi @Test
dan @ParameterizedTest
, dan cara bekerja dengan anotasi siklus hidup baru di JUnit 5. Anda juga akan melihat contoh singkat penggunaan tag filter, dan saya akan menunjukkan cara mengintegrasikan JUnit 5 dengan pustaka pernyataan pihak ketiga — dalam hal ini, Hamcrest . Terakhir, Anda akan mendapatkan pengantar tutorial singkat untuk mengintegrasikan JUnit 5 dengan Mockito, sehingga Anda dapat menulis pengujian unit yang lebih kuat untuk sistem dunia nyata yang kompleks.
Pengembangan yang digerakkan oleh pengujian
Jika Anda telah mengembangkan kode Java untuk jangka waktu tertentu, Anda mungkin sangat akrab dengan pengembangan berbasis pengujian, jadi saya akan membuat bagian ini singkat. Namun, penting untuk memahami mengapa kami menulis pengujian unit, serta strategi yang digunakan developer saat merancang pengujian unit.
Test-driven development (TDD) adalah proses pengembangan perangkat lunak yang menjalin pengkodean, pengujian, dan desain. Ini adalah pendekatan uji coba yang bertujuan untuk meningkatkan kualitas aplikasi Anda. Pengembangan berbasis pengujian ditentukan oleh siklus proses berikut:
- Tambahkan tes.
- Jalankan semua pengujian Anda dan amati kegagalan pengujian baru.
- Terapkan kodenya.
- Jalankan semua pengujian Anda dan amati pengujian baru yang berhasil.
- Refactor kode tersebut.
Gambar 1 menunjukkan siklus hidup TDD ini.

Ada dua tujuan untuk menulis tes sebelum menulis kode Anda. Pertama, ini memaksa Anda untuk memikirkan masalah bisnis yang Anda coba selesaikan. Misalnya, bagaimana skenario yang berhasil harus berperilaku? Kondisi apa yang harus gagal? Bagaimana mereka harus gagal? Kedua, pengujian pertama memberi Anda lebih percaya diri dalam pengujian Anda. Setiap kali saya menulis tes setelah menulis kode, saya selalu harus memecahkannya untuk memastikan bahwa mereka benar-benar menangkap kesalahan. Menulis tes terlebih dahulu menghindari langkah ekstra ini.
Menulis pengujian untuk jalur bahagia biasanya mudah: Dengan masukan yang baik, kelas harus mengembalikan respons deterministik. Tetapi menulis kasus uji negatif (atau kegagalan), terutama untuk komponen yang kompleks, bisa jadi lebih rumit.
Sebagai contoh, pertimbangkan untuk menulis tes untuk repositori database. Di jalur bahagia, kami memasukkan catatan ke dalam database dan menerima kembali objek yang dibuat, termasuk kunci yang dihasilkan. Pada kenyataannya, kita juga harus mempertimbangkan kemungkinan konflik, seperti memasukkan record dengan nilai kolom unik yang sudah dipegang oleh record lain. Selain itu, apa yang terjadi jika repositori tidak dapat terhubung ke database, mungkin karena nama pengguna atau kata sandi telah berubah? Apa yang terjadi jika ada kesalahan jaringan saat transit? Apa yang terjadi jika permintaan tidak selesai dalam batas waktu tunggu yang Anda tentukan?
Untuk membangun komponen yang kuat, Anda perlu mempertimbangkan semua skenario yang mungkin dan tidak mungkin, mengembangkan pengujian untuk mereka, dan menulis kode Anda untuk memenuhi pengujian tersebut. Nanti di artikel ini, kita akan melihat strategi untuk membuat skenario kegagalan yang berbeda, bersama dengan beberapa fitur baru di JUnit 5 yang dapat membantu Anda menguji skenario tersebut.
Mengadopsi JUnit 5
Jika Anda telah menggunakan JUnit selama beberapa waktu, beberapa perubahan di JUnit 5 akan menjadi penyesuaian. Berikut ringkasan tingkat tinggi tentang apa yang berbeda antara kedua versi:
- JUnit 5 sekarang dikemas dalam
org.junit.jupiter
grup, yang mengubah cara Anda memasukkannya ke dalam proyek Maven dan Gradle Anda. - JUnit 4 membutuhkan JDK minimal JDK 5; JUnit 5 membutuhkan minimal JDK 8.
- JUnit 4 ini
@Before
,@BeforeClass
,@After
, dan@AfterClass
penjelasan telah digantikan oleh@BeforeEach
,@BeforeAll
,@AfterEach
, dan@AfterAll
masing-masing. @Ignore
Anotasi JUnit 4 telah diganti dengan@Disabled
anotasi.- The
@Category
anotasi telah digantikan oleh@Tag
penjelasan. - JUnit 5 menambahkan satu set metode pernyataan baru.
- Runner telah diganti dengan ekstensi, dengan API baru untuk pelaksana ekstensi.
- JUnit 5 memperkenalkan asumsi yang menghentikan eksekusi pengujian.
- JUnit 5 mendukung kelas pengujian bersarang dan dinamis.
Kami akan menjelajahi sebagian besar fitur baru ini di artikel ini.
Pengujian unit dengan JUnit 5
Mari kita mulai dengan sederhana, dengan contoh end-to-end mengkonfigurasi proyek untuk menggunakan JUnit 5 untuk pengujian unit. Kode 1 menunjukkan MathTools
kelas yang metodenya mengubah pembilang dan penyebut menjadi a double
.
Kode 1. Contoh proyek JUnit 5 (MathTools.java)
package com.javaworld.geekcap.math; public class MathTools { public static double convertToDecimal(int numerator, int denominator) { if (denominator == 0) { throw new IllegalArgumentException("Denominator must not be 0"); } return (double)numerator / (double)denominator; } }
Kami memiliki dua skenario utama untuk menguji MathTools
kelas dan metodenya:
- Sebuah tes yang valid , di mana kami melewati bilangan bulat non-nol untuk pembilang dan penyebut.
- Sebuah skenario kegagalan , di mana kami melewati nilai nol untuk penyebut.
Kode 2 menunjukkan kelas uji JUnit 5 untuk menguji dua skenario ini.
Kode 2. Kelas uji JUnit 5 (MathToolsTest.java)
package com.javaworld.geekcap.math; import java.lang.IllegalArgumentException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class MathToolsTest { @Test void testConvertToDecimalSuccess() { double result = MathTools.convertToDecimal(3, 4); Assertions.assertEquals(0.75, result); } @Test void testConvertToDecimalInvalidDenominator() { Assertions.assertThrows(IllegalArgumentException.class, () -> MathTools.convertToDecimal(3, 0)); } }
In Listing 2, the testConvertToDecimalInvalidDenominator
method executes the MathTools::convertToDecimal
method inside an assertThrows
call. The first argument is the expected type of exception to be thrown. The second argument is a function that will throw that exception. The assertThrows
method executes the function and validates that the expected type of exception is thrown.
The Assertions class and its methods
The org.junit.jupiter.api.Test
annotation denotes a test method. Note that the @Test
annotation now comes from the JUnit 5 Jupiter API package instead of JUnit 4's org.junit
package. The testConvertToDecimalSuccess
method first executes the MathTools::convertToDecimal
method with a numerator of 3 and a denominator of 4, then asserts that the result is equal to 0.75. The org.junit.jupiter.api.Assertions
class provides a set of static
methods for comparing actual and expected results. The Assertions
class has the following methods, which cover most of the primitive data types:
assertArrayEquals
compares the contents of an actual array to an expected array.assertEquals
compares an actual value to an expected value.assertNotEquals
compares two values to validate that they are not equal.assertTrue
validates that the provided value is true.assertFalse
validates that the provided value is false.assertLinesMatch
compares two lists ofString
s.assertNull
validates that the provided value is null.assertNotNull
validates that the provided value is not null.assertSame
validates that two values reference the same object.assertNotSame
validates that two values do not reference the same object.assertThrows
validates that the execution of a method throws an expected exception (you can see this in thetestConvertToDecimalInvalidDenominator
example above).assertTimeout
validates that a supplied function completes within a specified timeout.assertTimeoutPreemptively
validates that a supplied function completes within a specified timeout, but once the timeout is reached it kills the function's execution.
If any of these assertion methods fail, the unit test is marked as failed. That failure notice will be written to the screen when you run the test, then saved in a report file.
Using delta with assertEquals
When using float
and double
values in an assertEquals
, you can also specify a delta
that represents a threshold of difference between the two. In our example we could have added a delta of 0.001, in case 0.75 was actually returned as 0.750001.
Analyzing your test results
In addition to validating a value or behavior, the assert
methods can also accept a textual description of the error, which can help you diagnose failures. For example:
Assertions.assertEquals(0.75, result, "The MathTools::convertToDecimal value did not return the correct value of 0.75 for 3/4"); Assertions.assertEquals(0.75, result, () -> "The MathTools::convertToDecimal value did not return the correct value of 0.75 for 3/4");
The output will show the expected value of 0.75 and the actual value. It will also display the specified message, which can help you understand the context of the error. The difference between the two variations is that the first one always creates the message, even if it is not displayed, whereas the second one only constructs the message if the assertion fails. In this case, the construction of the message is trivial, so it doesn't really matter. Still, there is no need to construct an error message for a test that passes, so it's usually a best practice to use the second style.
Finally, if you're using an IDE like IntelliJ to run your tests, each test method will be displayed by its method name. This is fine if your method names are readable, but you can also add a @DisplayName
annotation to your test methods to better identify the tests:
@Test @DisplayName("Test successful decimal conversion") void testConvertToDecimalSuccess() { double result = MathTools.convertToDecimal(3, 4); Assertions.assertEquals(0.751, result); }
Running your unit test
In order to run JUnit 5 tests from a Maven project, you need to include the maven-surefire-plugin
in the Maven pom.xml
file and add a new dependency. Listing 3 shows the pom.xml
file for this project.
Listing 3. Maven pom.xml for an example JUnit 5 project
4.0.0 com.javaworld.geekcap junit5 jar 1.0-SNAPSHOT org.apache.maven.plugins maven-compiler-plugin 3.8.1 8 8 org.apache.maven.plugins maven-surefire-plugin 3.0.0-M4 junit5 //maven.apache.org org.junit.jupiter junit-jupiter 5.6.0 test
JUnit 5 dependencies
JUnit 5 packages its components in the org.junit.jupiter
group and we need to add the junit-jupiter
artifact, which is an aggregator artifact that imports the following dependencies:
junit-jupiter-api
defines the API for writing tests and extensions.junit-jupiter-engine
adalah implementasi mesin uji yang menjalankan pengujian unit.junit-jupiter-params
memberikan dukungan untuk pengujian berparameter.
Selanjutnya, kita perlu menambahkan maven-surefire-plugin
plug-in build untuk menjalankan pengujian.
Terakhir, pastikan untuk menyertakan maven-compiler-plugin
dengan versi Java 8 atau yang lebih baru, sehingga Anda akan dapat menggunakan fitur Java 8 seperti lambda.
Menjalankannya!
Gunakan perintah berikut untuk menjalankan kelas pengujian dari IDE Anda atau dari Maven:
mvn clean test
Jika Anda berhasil, Anda akan melihat keluaran yang mirip dengan berikut ini:
[INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running com.javaworld.geekcap.math.MathToolsTest [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.04 s - in com.javaworld.geekcap.math.MathToolsTest [INFO] [INFO] Results: [INFO] [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 3.832 s [INFO] Finished at: 2020-02-16T08:21:15-05:00 [INFO] ------------------------------------------------------------------------