Fungsi di Rust tidak selalu menolak input yang buruk dengan Result — beberapa fungsi memilih membela diri dengan panic! ketika menerima input yang mustahil diproses, seperti persentase di atas 100 atau indeks di luar rentang. Kita sudah melihat perilaku panic ini di Section Penanganan Error dan di Chapter Integer Overflow, namun kini muncul masalah arah yang baru: aturan main test Rust menyatakan test yang mengalami panic dinyatakan gagal. Bagaimana cara menguji bahwa fungsi kita memang benar-benar panic ketika seharusnya? Jawabannya adalah attribute #[should_panic], yang membalik aturan kegagalan untuk test bersangkutan: panic menjadi tanda lulus, dan test yang selesai tanpa panic justru dinyatakan gagal. Chapter ini menguji fungsi validasi persen diskon, lalu menajamkan attribute dengan syarat expected agar test tidak mudah tertipu oleh panic yang salah penyebab.
Masalah: Panic adalah Tanda Gagal #
Mari kita rasa dulu masalahnya. Proyek promo berisi fungsi potongan_persen yang menolak persen di atas 100 dengan panic!, karena persentase diskon melebihi seratus tidak punya makna bisnis. Test biasa yang memanggil potongan_persen(150) akan berjalan lurus ke pemanggilan panic! di dalam fungsi — dan test harness menandainya FAILED, persis seperti assertion yang gagal. Padahal justru perilaku inilah yang ingin kita pastikan. Attribute #[should_panic] di atas fungsi test mengubah penilaian harness: harness tetap menjalankan fungsi test seperti biasa, tetapi kini membungkusnya dengan penangkap panic. Test dinyatakan lulus hanya jika panic benar-benar terjadi di dalamnya.
should_panic Dasar #
Buat proyek dengan cargo new promo --lib, lalu isi src/lib.rs berikut:
pub fn potongan_persen(persen: u64) -> u64 {
if persen > 100 {
panic!("persen diskon tidak boleh lebih dari 100: {}", persen);
}
persen
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn persen_valid_diterima() {
assert_eq!(potongan_persen(20), 20);
}
#[test]
#[should_panic]
fn persen_lebih_dari_seratus_menyebabkan_panic() {
potongan_persen(150);
}
}Jalankan test:
cargo testOutput yang Dihasilkan:
Compiling promo v0.1.0 (/tmp/w4e-should-panic/promo)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.10s
Running unittests src/lib.rs (target/debug/deps/promo-e08a77cfce56433b)
running 2 tests
test tests::persen_valid_diterima ... ok
test tests::persen_lebih_dari_seratus_menyebabkan_panic - should panic ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests promo
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00sPerhatikan tanda khusus pada laporan: test tests::persen_lebih_dari_seratus_menyebabkan_panic - should panic ... ok. Sisipan - should panic menandai bahwa harness menjalankan test dengan ekspektasi kebalikan. Mari kita bedah kode di atas:
- Baris 2-4 (
if persen > 100 { panic!(...) }): jalur pertahanan fungsi. Pemanggilanpotongan_persen(150)masuk ke cabang ini, memicupanicdengan pesan yang menyertakan nilai persen yang melanggar. - Baris 12-13 (
#[test] fn persen_valid_diterima()): test jalur normal — nilai20diterima dan dikembalikan apa adanya. Kombinasi test jalur normal dan jalurpanicdalam satu file memastikan fungsi tidak hanya galak, tetapi juga benar-benar menerima input yang sah. - Baris 17-18 (
#[test]+#[should_panic]): dua attribute bertumpuk di atas fungsi yang sama.#[test]tetap mendaftarkan fungsi ke harness, sedangkan#[should_panic]dibaca harness sebagai instruksi pembalik nilai kelulusan; keduanya wajib hadir. - Baris 20 (
potongan_persen(150);): isi test hanya satu pemanggilan. Test ini tidak memerlukanassert!karena keberhasilannya ditentukan oleh munculnyapanic, bukan oleh nilai kembalian.
Menyaring Panic dengan expected #
Bentuk dasar #[should_panic] punya kelemahan yang halus namun serius: attribute ini meluluskan test untuk panic apa pun — termasuk panic yang muncul dari sebab yang salah. Bayangkan suatu saat fungsi berubah dan potongan_persen(150) malah panic karena kesalahan pembagian nol; test kita tetap hijau padahal perilakunya sudah tidak sesuai desain. Syarat expected menutup celah ini dengan menuntut pesan panic memuat substring tertentu:
#[test]
#[should_panic(expected = "persen diskon tidak boleh lebih dari 100")]
fn persen_lebih_dari_seratus_panic_dengan_pesan_yang_tepat() {
potongan_persen(150);
}Pembandingan yang dilakukan harness adalah pencarian substring, bukan kesamaan penuh — pesan aktual persen diskon tidak boleh lebih dari 100: 150 memuat teks yang kita minta, jadi test lulus meski ada akhiran : 150 yang tidak kita tulis. Kebiasaan yang baik adalah memilih bagian pesan yang stabil dan khas: cukup panjang agar tidak kebetulan cocok dengan panic lain, cukup pendek agar tidak rapuh terhadap perubahan kecil pada format pesan.
Dua Cara Test should_panic Gagal #
Attribute #[should_panic] memiliki dua jalur kegagalan, dan keduanya layak kita lihat wujudnya. Jalur pertama: panic terjadi, tetapi pesannya tidak memuat substring expected. Kita ganti isi expected menjadi teks yang tidak ada hubungannya:
#[test]
#[should_panic(expected = "kata sandi salah")]
fn persen_lebih_dari_seratus_panic_dengan_pesan_yang_tepat() {
potongan_persen(150);
}Output yang Dihasilkan:
Compiling promo v0.1.0 (/tmp/w4e-should-panic/promo)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.08s
Running unittests src/lib.rs (target/debug/deps/promo-e08a77cfce56433b)
running 1 test
test tests::persen_lebih_dari_seratus_panic_dengan_pesan_yang_tepat - should panic ... FAILED
failures:
---- tests::persen_lebih_dari_seratus_panic_dengan_pesan_yang_tepat stdout ----
thread 'tests::persen_lebih_dari_seratus_panic_dengan_pesan_yang_tepat' (16614) panicked at src/lib.rs:3:9:
persen diskon tidak boleh lebih dari 100: 150
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
note: panic did not contain expected string
panic message: "persen diskon tidak boleh lebih dari 100: 150"
expected substring: "kata sandi salah"
failures:
tests::persen_lebih_dari_seratus_panic_dengan_pesan_yang_tepat
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass `--lib`Laporan kegagalan jenis ini sangat komunikatif. Baris panicked at src/lib.rs:3:9 beserta pesan di bawahnya menunjukkan panic yang benar-benar terjadi, lalu dua baris note membandingkan pihak yang berseteru: panic message berisi pesan aktual lengkap dengan tanda kutip, dan expected substring berisi teks yang kita minta. Membaca keduanya berdampingan langsung memperlihatkan ketidakcocokannya.
Jalur kegagalan kedua: test selesai tanpa panic sama sekali. Isi test kita ganti dengan pemanggilan yang sah:
#[test]
#[should_panic]
fn persen_lebih_dari_seratus_menyebabkan_panic() {
potongan_persen(20);
}Output yang Dihasilkan:
Compiling promo v0.1.0 (/tmp/w4e-should-panic/promo)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.08s
Running unittests src/lib.rs (target/debug/deps/promo-e08a77cfce56433b)
running 1 test
test tests::persen_lebih_dari_seratus_menyebabkan_panic - should panic ... FAILED
failures:
---- tests::persen_lebih_dari_seratus_menyebabkan_panic stdout ----
note: test did not panic as expected at src/lib.rs:14:8
failures:
tests::persen_lebih_dari_seratus_menyebabkan_panic
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass `--lib`Pesan test did not panic as expected at src/lib.rs:14:8 menunjuk ke deklarasi fungsi test pada Baris 14 — pemanggilan potongan_persen(20) di dalamnya selesai normal, tidak ada panic yang tertangkap, maka test gugur. Kegagalan jenis ini biasanya menandakan jalur pertahanan fungsi tidak terpicu: mungkin kondisi batasnya salah atau argument yang dikirim tidak sampai ke cabang panic.
Ringkasan #
Attribute #[should_panic] membalik penilaian harness untuk satu test: panic yang tertangkap berarti lulus, tidak ada panic berarti gagal dengan pesan test did not panic as expected. Syarat expected = "..." menuntut pesan panic memuat substring tertentu sehingga test hanya menerima panic dari penyebab yang benar; ketidakcocokan dilaporkan dengan perbandingan panic message lawan expected substring. Pada Chapter-Chapter sebelumnya seluruh mekanisme kegagalan kita bertumpu pada panic; Chapter berikutnya menawarkan gaya penulisan test yang lebih tenang — test yang mengembalikan Result, lengkap dengan operator ? untuk merambatkan galat.