Test yang baik berangkat dari kondisi awal yang bersih dan tidak meninggalkan sisa setelah selesai. Sayangnya, kondisi bersih jarang datang gratis: keranjang uji perlu direset, sambungan basis data perlu dibuka lalu ditutup, berkas sementara perlu dihapus. JUnit Jupiter menyediakan empat anotasi siklus hidup — @BeforeAll, @BeforeEach, @AfterEach, dan @AfterAll — sebagai tempat terpusat untuk pekerjaan penyiapan dan pembersihan tersebut. Pada Chapter ini kita akan memetakan keempat anotasi itu, membuktikan urutan eksekusinya dengan demo Java murni yang mencetak setiap tahap, lalu belajar memilih hook yang tepat agar test tetap terisolasi.
Empat Anotasi Siklus Hidup #
Keempat hook dikelompokkan menjadi dua pasang. Pasangan level class berjalan sekali untuk seluruh class uji; pasangan level test berjalan mengelilingi setiap method uji:
| Anotasi | Berjalan | Kegunaan umum | Syarat default |
|---|---|---|---|
@BeforeAll | Sekali sebelum seluruh test | Membuka sambungan, memuat berkas besar | Harus static |
@BeforeEach | Sebelum tiap test | Mereset state, membuat objek uji segar | Boleh non-static |
@AfterEach | Setelah tiap test | Membersihkan sisa per test | Boleh non-static |
@AfterAll | Sekali setelah seluruh test | Menutup sambungan, mematikan sumber daya | Harus static |
Satu detail yang sering mengejutkan pemula: Jupiter membuat instance baru dari class uji untuk setiap method test. Field instance kembali ke nilai awal untuk tiap test tanpa perlu usaha apa pun dari kita — isolasi gratis. State yang benar-benar bersama antar test hanya ada pada field static, dan di situlah hook level class berperan.
Demo Urutan dari Java Murni #
Daripada menghafal diagram, kita buktikan urutannya. Program berikut menyalin perilaku Jupiter: satu penyiapan level class, instance segar per test, dan pembersihan level test di sekitar setiap method uji:
import java.util.List;
import java.util.function.Consumer;
public class ContohSiklusUji {
private static int jumlahInstance = 0;
private final int nomorInstance;
ContohSiklusUji() {
jumlahInstance++;
nomorInstance = jumlahInstance;
}
// ---- padanan @BeforeAll dan @AfterAll: sekali untuk seluruh class ----
static void sebelumSemua() {
System.out.println("@BeforeAll : membuka sambungan ke basis data uji");
}
static void sesudahSemua() {
System.out.println("@AfterAll : menutup sambungan ke basis data uji");
}
// ---- padanan @BeforeEach dan @AfterEach: sekali untuk tiap test ----
void sebelumTiap() {
System.out.println("@BeforeEach : siapkan instance uji ke-" + nomorInstance);
}
void sesudahTiap() {
System.out.println("@AfterEach : bersihkan instance uji ke-" + nomorInstance);
}
// ---- dua method uji ----
void ujiTambahSatuItem() {
System.out.println("@Test : ujiTambahSatuItem");
}
void ujiKeranjangKosong() {
System.out.println("@Test : ujiKeranjangKosong");
}
public static void main(String[] args) {
sebelumSemua();
List<Consumer<ContohSiklusUji>> semuaUji = List.of(
uji -> uji.ujiTambahSatuItem(),
uji -> uji.ujiKeranjangKosong()
);
for (Consumer<ContohSiklusUji> uji : semuaUji) {
ContohSiklusUji instance = new ContohSiklusUji(); // instance segar per test
instance.sebelumTiap();
uji.accept(instance);
instance.sesudahTiap();
}
sesudahSemua();
}
}Output yang Dihasilkan:
@BeforeAll : membuka sambungan ke basis data uji
@BeforeEach : siapkan instance uji ke-1
@Test : ujiTambahSatuItem
@AfterEach : bersihkan instance uji ke-1
@BeforeEach : siapkan instance uji ke-2
@Test : ujiKeranjangKosong
@AfterEach : bersihkan instance uji ke-2
@AfterAll : menutup sambungan ke basis data ujiUrutan cetakan itu adalah kontrak yang dipegang runner Jupiter. Bedah baris-per-baris untuk bagian paling penting:
- Baris
private static int jumlahInstance = 0;: penghitung level class — padanan fieldstaticyang nilainya bertahan lintas instance, dipakai untuk menomori setiap instance uji. - Baris isi constructor
jumlahInstance++; nomorInstance = jumlahInstance;: setiap kali runner membuat instance baru, nomor berjalan maju; dari sini kita bisa memverifikasi bahwa dua test berjalan pada dua instance berbeda. - Baris
static void sebelumSemua()dansesudahSemua(): padanan@BeforeAll/@AfterAll— dibuatstaticmeniru syarat default Jupiter untuk hook level class. - Baris
ContohSiklusUji instance = new ContohSiklusUji();: inti siklus Jupiter — satu instance segar per test, diletakkan di dalam perulangan sehingga state instance tidak bocor ke test berikutnya. - Baris
instance.sebelumTiap(); ... instance.sesudahTiap();: hook level test mengelilingi tepat satu method uji; jika ada tiga test, pasangan ini berjalan tiga kali. - Baris
sesudahSemua();di akhirmain: pembersihan level class tetap berjalan setelah seluruh test selesai — bahkan pada Jupiter,@AfterAlltetap dipanggil walau ada test yang gagal di tengah jalan.
Kapan Memakai Hook yang Mana #
Aturan praktisnya mudah: mulai dari @BeforeEach untuk semua kebutuhan penyiapan, naik ke @BeforeAll hanya jika penyiapan itu mahal dan hasilnya berupa data baca-saja. Membuka koneksi HTTP atau memuat model machine learning sekali untuk dipakai ratusan test adalah kandidat @BeforeAll yang tepat. Sebaliknya, objek yang akan dimodifikasi test — keranjang, rekening, sesi — sebaiknya dibuat di @BeforeEach atau langsung di field instance agar tiap test menerima salinan segar.
Siklus PER_CLASS #
Sejak JUnit 5, syarat static pada hook level class dapat dilonggarkan dengan mengubah mode lifecycle class uji:
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class LaporanUjiTest {
@BeforeAll
void sekaliSebelumSemua() { // kini boleh non-static
// memuat data yang dibutuhkan seluruh test
}
}Dengan PER_CLASS, runner memakai satu instance untuk seluruh test di class tersebut — menguntungkan bila class uji membawa state mahal yang ingin dibangun sekali, tetapi menambah risiko kebocoran state antar test. Mode default PER_METHOD tetap menjadi pilihan teraman untuk mayoritas kasus; perlakukan PER_CLASS sebagai optimasi yang disengaja, bukan kebiasaan.
Sekarang kita memahami kapan penyiapan berjalan. Chapter berikutnya menaikkan kualitas penyiapan itu sendiri: menyusun test fixture yang stabil dan mudah dirawat lewat builder dan object mother.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
ContohSiklusUji.java
Unduh berkasimport java.util.List;
import java.util.function.Consumer;
public class ContohSiklusUji {
private static int jumlahInstance = 0;
private final int nomorInstance;
ContohSiklusUji() {
jumlahInstance++;
nomorInstance = jumlahInstance;
}
// ---- padanan @BeforeAll dan @AfterAll: sekali untuk seluruh class ----
static void sebelumSemua() {
System.out.println("@BeforeAll : membuka sambungan ke basis data uji");
}
static void sesudahSemua() {
System.out.println("@AfterAll : menutup sambungan ke basis data uji");
}
// ---- padanan @BeforeEach dan @AfterEach: sekali untuk tiap test ----
void sebelumTiap() {
System.out.println("@BeforeEach : siapkan instance uji ke-" + nomorInstance);
}
void sesudahTiap() {
System.out.println("@AfterEach : bersihkan instance uji ke-" + nomorInstance);
}
// ---- dua method uji ----
void ujiTambahSatuItem() {
System.out.println("@Test : ujiTambahSatuItem");
}
void ujiKeranjangKosong() {
System.out.println("@Test : ujiKeranjangKosong");
}
public static void main(String[] args) {
sebelumSemua();
List<Consumer<ContohSiklusUji>> semuaUji = List.of(
uji -> uji.ujiTambahSatuItem(),
uji -> uji.ujiKeranjangKosong()
);
for (Consumer<ContohSiklusUji> uji : semuaUji) {
ContohSiklusUji instance = new ContohSiklusUji(); // instance segar per test
instance.sebelumTiap();
uji.accept(instance);
instance.sesudahTiap();
}
sesudahSemua();
}
}