Kode yang tidak diuji hanyalah tebakan yang kebetulan berjalan. Ekosistem Java menjawabnya dengan tiga pustaka pokok: JUnit sebagai framework uji, Mockito untuk mengganti dependensi yang mahal, dan AssertJ untuk assertion yang mudah dibaca. Chapter ini mengumpulkan rujukan resmi ketiganya dan menunjukkan jalurnya dari bawah: pola uji sederhana dengan JDK murni, lalu padanannya di JUnit 5. Dengan begitu framework terasa sebagai otomatisasi pola lama, bukan sesuatu yang abstrak.
Tiga Pilar Pengujian di Ekosistem Java #
Pembagian perannya tegas. JUnit menyediakan runner yang menemukan method uji, menjalankannya, dan melaporkan hasilnya — plus assertion dasar seperti assertEquals dan assertThrows. Mockito menyediakan objek palsu (test double) untuk menggantikan dependensi yang lambat atau berisiko saat diuji, misalnya layanan web atau database. AssertJ melengkapi keduanya dengan assertion berantai yang terbaca seperti kalimat. Ketiganya dinyatakan sebagai dependensi di berkas build hasil chapter sebelumnya.
Pola Dasar: Harness Berbasis main #
Sebelum memasang framework, mari kita bangun polanya dari nol. Semua framework uji pada dasarnya melakukan tiga hal: menjalankan potongan pemeriksaan, mencatat lulus atau gagal, dan melaporkan ringkasan. Program berikut mengerjakan ketiganya dengan JDK murni, menguji fungsi tarif parkir yang penuh batas aturan:
public class UjiTarif {
static int lulus = 0;
static int gagal = 0;
public static void main(String[] args) {
periksa("tarif 0 menit (gratis)", 0, tarif(0));
periksa("tarif 14 menit (masih gratis)", 0, tarif(14));
periksa("tarif 15 menit (mulai dikenakan)", 5000, tarif(15));
periksa("tarif 60 menit (jam pertama penuh)", 5000, tarif(60));
periksa("tarif 61 menit (jam kedua dimulai)", 8000, tarif(61));
periksa("tarif 120 menit (dua jam pas)", 8000, tarif(120));
periksa("tarif 240 menit (empat jam)", 14000, tarif(240));
periksa("tarif 600 menit (batas harian)", 20000, tarif(600));
System.out.println();
System.out.println("Ringkasan: " + lulus + " PASS, " + gagal + " FAIL");
if (gagal > 0) {
System.exit(1);
}
}
// Bandingkan nilai harapan dengan nilai nyata, catat hasilnya.
static void periksa(String nama, int harapan, int nyata) {
if (harapan == nyata) {
lulus++;
System.out.println("PASS " + nama);
} else {
gagal++;
System.out.println("FAIL " + nama + ": harapan " + harapan + ", nyata " + nyata);
}
}
// 0-14 menit gratis; 15-60 menit Rp5.000; setelahnya Rp3.000 per jam
// dimulai; total dibatasi Rp20.000 per hari.
static int tarif(int menit) {
if (menit < 15) {
return 0;
}
int total = 5000 + 3000 * ((menit - 60 + 59) / 60); // pembagian bulat ke atas
return Math.min(total, 20000);
}
}Output yang Dihasilkan:
PASS tarif 0 menit (gratis)
PASS tarif 14 menit (masih gratis)
PASS tarif 15 menit (mulai dikenakan)
PASS tarif 60 menit (jam pertama penuh)
PASS tarif 61 menit (jam kedua dimulai)
PASS tarif 120 menit (dua jam pas)
PASS tarif 240 menit (empat jam)
PASS tarif 600 menit (batas harian)
Ringkasan: 8 PASS, 0 FAILMari kita telaah pola yang bekerja di baliknya:
- Baris 7-14 (delapan pemanggilan
periksa): setiap baris adalah satu kasus uji di batas aturan — gratis di bawah 15 menit, dikenakan mulai menit ke-15, jam kedua dimulai pada menit ke-61, dan batas harian tercapai pada 600 menit. - Baris 23-31 (
periksa): method bantu membandingkan nilai harapan dengan nilai nyata, mencetakPASSatauFAIL, dan mengumpulkan hitungan — inti mekanis yang diotomatisasi JUnit. - Baris 17-19 (
System.exit(1)): exit code bukan nol memberi tahu alat build bahwa rangkaian uji gagal; JUnit memakai mekanisme serupa lewat launcher-nya. - Baris 35-41 (
tarif): fungsi murni tanpa keadaan bagian dalam sehingga hasil hanya bergantung pada argumen — sifat yang memudahkan pengujian.
Padanan dalam JUnit 5 #
JUnit 5 mengotomatiskasi pola di atas: annotation @Test menandai method uji, assertEquals membandingkan nilai, dan runner mengumpulkan semuanya menjadi laporan. Padanan berikut mengasumsikan fungsi tarif dipindahkan ke class TarifParkir agar bisa diuji terpisah:
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class TarifParkirTest {
@Test
void batasGratisDanJamPertama() {
assertEquals(0, TarifParkir.tarif(0));
assertEquals(0, TarifParkir.tarif(14));
assertEquals(5_000, TarifParkir.tarif(15));
assertEquals(5_000, TarifParkir.tarif(60));
}
@Test
void jamBerikutnyaDanBatasHarian() {
assertEquals(8_000, TarifParkir.tarif(61));
assertEquals(20_000, TarifParkir.tarif(600));
}
}Setiap method @Test berdiri sendiri: bila satu assertion gagal, JUnit mencatatnya lalu melanjutkan ke method berikutnya. Panduan lengkap — anotasi lifecycle sampai parameterized test — dibaca di junit.org/junit5/docs/current/user-guide/, alamat yang selalu menunjuk versi terkini.
Mockito untuk Dependensi yang Mahal #
Sebagian kode bergantung pada hal yang mahal atau berisiko saat diuji: layanan web, database, atau jam nyata. Mockito membuat objek palsu dari sebuah interface, lalu memprogram perilakunya lewat when(...).thenReturn(...). Kode yang diuji tetap kode produksi asli — hanya tepiannya yang diganti:
interface KatalogProduk {
int harga(String kode);
}
class Kasir {
private final KatalogProduk katalog;
Kasir(KatalogProduk katalog) {
this.katalog = katalog;
}
int total(String... kode) {
int jumlah = 0;
for (String k : kode) {
jumlah += katalog.harga(k);
}
return jumlah;
}
}
// Di dalam method uji:
KatalogProduk katalogPalsu = Mockito.mock(KatalogProduk.class);
Mockito.when(katalogPalsu.harga("BRG-01")).thenReturn(5_000);
Mockito.when(katalogPalsu.harga("BRG-02")).thenReturn(3_000);
Kasir kasir = new Kasir(katalogPalsu);
assertEquals(8_000, kasir.total("BRG-01", "BRG-02"));Dengan begitu pengujian Kasir tidak membutuhkan database harga. Referensi lengkap fitur Mockito — mock, verify, argument matcher — ada di situs resminya dan Javadoc pada tabel rujukan di akhir chapter.
AssertJ untuk Pemeriksaan yang Terbaca #
Assertion bawaan JUnit memadai, tetapi AssertJ membuat rangkaian pemeriksaan terbaca seperti kalimat:
List<String> nama = new ArrayList<>(List.of("Budi", "Siti", "Dewi"));
assertThat(nama)
.hasSize(3)
.containsExactly("Budi", "Siti", "Dewi")
.doesNotContain("Ani");
assertThat(" Budi Santoso ").isNotBlank().contains("Santoso");Setiap rantai berhenti pada kegagalan pertama dan pesan galatnya menyebut nilai nyata. Dokumentasi resmi beserta daftar lengkap assertion ada di assertj.github.io/doc/.
Kumpulan Rujukan Resmi #
Tabel berikut mengumpulkan alamat resmi yang paling sering dibuka saat bekerja dengan ketiga pustaka:
| Rujukan | Alamat | Manfaat |
|---|---|---|
| JUnit 5 — User Guide | junit.org/junit5/docs/current/user-guide/ | Panduan resmi menulis uji |
| JUnit 5 — API | junit.org/junit5/docs/current/api/ | Javadoc annotation dan assertion |
| Mockito — Situs resmi | site.mockito.org/ | Fitur inti dan contoh pemakaian |
| Mockito — Javadoc | javadoc.io/doc/org.mockito/mockito-core/latest/org.mockito/org/mockito/Mockito.html | Referensi class Mockito |
| AssertJ — Dokumentasi | assertj.github.io/doc/ | Panduan assertion berantai |
Dengan tiga rujukan itu, fondasi pengujian kita lengkap dari pola main sederhana sampai framework modern. Logika yang teruji hanyalah separuh cerita: aplikasi nyata juga harus tahan terhadap masukan berbahaya dan akses yang tidak sah. Chapter berikutnya membuka rak sumber keamanan Java, dari panduan secure coding Oracle sampai proyek OWASP.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
UjiTarif.java
Unduh berkaspublic class UjiTarif {
static int lulus = 0;
static int gagal = 0;
public static void main(String[] args) {
periksa("tarif 0 menit (gratis)", 0, tarif(0));
periksa("tarif 14 menit (masih gratis)", 0, tarif(14));
periksa("tarif 15 menit (mulai dikenakan)", 5000, tarif(15));
periksa("tarif 60 menit (jam pertama penuh)", 5000, tarif(60));
periksa("tarif 61 menit (jam kedua dimulai)", 8000, tarif(61));
periksa("tarif 120 menit (dua jam pas)", 8000, tarif(120));
periksa("tarif 240 menit (empat jam)", 14000, tarif(240));
periksa("tarif 600 menit (batas harian)", 20000, tarif(600));
System.out.println();
System.out.println("Ringkasan: " + lulus + " PASS, " + gagal + " FAIL");
if (gagal > 0) {
System.exit(1);
}
}
// Bandingkan nilai harapan dengan nilai nyata, catat hasilnya.
static void periksa(String nama, int harapan, int nyata) {
if (harapan == nyata) {
lulus++;
System.out.println("PASS " + nama);
} else {
gagal++;
System.out.println("FAIL " + nama + ": harapan " + harapan + ", nyata " + nyata);
}
}
// 0-14 menit gratis; 15-60 menit Rp5.000; setelahnya Rp3.000 per jam
// dimulai; total dibatasi Rp20.000 per hari.
static int tarif(int menit) {
if (menit < 15) {
return 0;
}
int total = 5000 + 3000 * ((menit - 60 + 59) / 60); // pembagian bulat ke atas
return Math.min(total, 20000);
}
}