Sejak Section Penanganan Error kita menjauhkan panic dari kode produksi dan memilih Result sebagai bahasa galat yang sopan. Namun di dalam test, semua mekanisme kegagalan yang kita pelajari sejauh ini — assert!, assert_eq!, assert_ne!, hingga #[should_panic] — tetap bekerja lewat panic. Ada satu ketidaknyamanan teknis di balik situasi tersebut: begitu kita menguji fungsi yang mengembalikan Result, kita harus membongkar nilainya dengan match atau memeriksa is_ok()/is_err() sebelum bisa memakai assertion. Rust menawarkan alternatif yang lebih tenang: fungsi test boleh memiliki tipe nilai kembalian Result<(), String>. Test seperti ini dinyatakan lulus ketika mengembalikan Ok(()) dan gagal ketika mengembalikan Err — dan yang paling penting, operator ? menjadi sah dipakai di dalam test, sehingga galat dari fungsi yang diuji bisa dirambatkan langsung menjadi kegagalan test. Chapter ini menguji fungsi validasi_email sederhana dengan dua gaya sekaligus agar kita bisa merasakan perbedaannya.
Cara Kerja Test Ber-Result #
Mekanisme dasarnya sederhana. Test harness bawaan Rust mengevaluasi hasil pemanggilan setiap fungsi test. Untuk test bertipe (), harness hanya mengamati ada tidaknya panic — seperti pada Chapter-Chapter sebelumnya. Untuk test yang mengembalikan Result<(), E> dengan tipe galat apa pun yang mencetak dengan Debug, harness memeriksa nilainya: Ok(()) menandai test lulus, sedangkan Err(e) menandai test gagal dan nilai e dicetak pada bagian laporan kegagalan. Baris test nama_test ... ok tetap tampil di laporan seperti biasa; perbedaannya hanya terjadi di dalam mesin penilaiannya.
Tipe galat paling umum untuk test sehari-hari adalah String, karena pesan galatnya bebas kita tulis. Aturan penulisannya sama seperti Result pada umumnya: tanda tanya ? di akhir baris merambatkan galat, dan Ok(()) menjadi baris penutup yang menyatakan seluruh pemeriksaan berhasil.
Menguji validasi_email dengan Result #
Buat proyek dengan cargo new validasi --lib, lalu isi src/lib.rs berikut:
pub fn validasi_email(email: &str) -> Result<(), String> {
if email.is_empty() {
return Err(String::from("email tidak boleh kosong"));
}
if !email.contains('@') {
return Err(format!("email harus mengandung '@': {}", email));
}
if email.starts_with('@') || email.ends_with('@') {
return Err(String::from("tanda '@' harus diapit teks di kedua sisi"));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn email_benar_lolos_validasi() -> Result<(), String> {
validasi_email("[email protected]")?;
Ok(())
}
#[test]
fn email_tanpa_at_ditolak() -> Result<(), String> {
let hasil = validasi_email("budi.example.com");
assert!(hasil.is_err());
Ok(())
}
}Jalankan test:
cargo testOutput yang Dihasilkan:
Compiling validasi v0.1.0 (/tmp/w4e-test-dengan-result/validasi)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.27s
Running unittests src/lib.rs (target/debug/deps/validasi-07594211b669c5ed)
running 2 tests
test tests::email_benar_lolos_validasi ... ok
test tests::email_tanpa_at_ditolak ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests validasi
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00sMari kita bedah kode di atas:
- Baris 1-12 (
pub fn validasi_email(...)): fungsi produksi bergayaResultpenuh — tiga syarat diperiksa berurutan dan tiap pelanggaran mengembalikanErrdengan pesan berbeda. Nilai suksesnyaOk(())karena fungsi hanya memeriksa, tidak menghasilkan data. - Baris 19 (
fn email_benar_lolos_validasi() -> Result<(), String>): signature test yang menjadi inti Chapter ini. Tanda tangan ini menggantikan()bawaan; harness kini menilai test dari nilai kembaliannya. - Baris 20 (
validasi_email("[email protected]")?;): operator?merambatkan kemungkinan galat. Jika fungsi mengembalikanErr, operator?menghentikan test dan menjadikanErrtersebut nilai kembalian test — test otomatis gagal. JikaOk, eksekusi lanjut ke baris berikutnya. Inilah kenyamanan utama gaya Result: satu karakter menggantikan blokmatchpemeriksa manual. - Baris 21 (
Ok(())): baris terakhir test tanpa?, dievaluasi sebagai nilai kembalian fungsi — sinyal resmi bahwa seluruh pemeriksaan lulus. - Baris 26-28 (
let hasil = ...; assert!(hasil.is_err());): gaya campuran tetap sah di dalam test ber-Result. Methodis_err()milik enumResultmemberibool, laluassert!menilainya seperti biasa. PenutupOk(())tetap wajib karena signature test menuntutnya.
Operator ? juga memudahkan merangkai banyak pemanggilan dalam satu test tanpa bertingkat:
#[test]
fn rangkaian_email_valid() -> Result<(), String> {
validasi_email("[email protected]")?;
validasi_email("[email protected]")?;
Ok(())
}Dua pemanggilan berjajar seperti dua statement biasa. Bila validasi pertama gagal, ? langsung memutus test dengan pesan galatnya; bila keduanya lolos, Ok(()) menutup test dengan lulus. Gaya penulisan seperti ini terasa alami karena bentuknya persis seperti kode produksi yang memanggil fungsi fallible secara berurutan.
Saat Test Ber-Result Gagal #
Sekarang kita lihat wujud kegagalannya. Test berikut sengaja mengharapkan email kosong lolos validasi — kebalikan dari perilaku fungsi:
#[test]
fn email_kosong_seharusnya_ditolak() -> Result<(), String> {
validasi_email("")?;
Ok(())
}Output yang Dihasilkan:
Compiling validasi v0.1.0 (/tmp/w4e-test-dengan-result/validasi)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.09s
Running unittests src/lib.rs (target/debug/deps/validasi-07594211b669c5ed)
running 1 test
test tests::email_kosong_seharusnya_ditolak ... FAILED
failures:
---- tests::email_kosong_seharusnya_ditolak stdout ----
Error: "email tidak boleh kosong"
failures:
tests::email_kosong_seharusnya_ditolak
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`Ciri khas kegagalan test ber-Result adalah baris Error: "email tidak boleh kosong" — tanpa panicked at, tanpa nomor thread, tanpa lokasi file. Nilai Err dari fungsi mengalir apa adanya menjadi nilai kembalian test, lalu harness mencetaknya dengan format {:?} milik Debug; tanda kutip yang mengapit pesan berasal dari format Debug milik String. Ketidakadaan lokasi baris adalah trade-off yang perlu kita ketahui: laporan ini lebih tenang, tetapi tidak menunjuk assertion mana yang gagal. Pada contoh di atas, pemanggilan validasi_email("") pada Baris 20 dari src/lib.rs yang utuh-lah yang merambatkan galat lewat ?.
Memilih Gaya: Result atau assert #
Kedua gaya bisa dipakai bergantian, bahkan bercampur dalam satu test seperti contoh email_tanpa_at_ditolak. Pedoman praktisnya sebagai berikut. Gunakan tipe kembalian Result ketika fungsi yang diuji sudah mengembalikan Result — operator ? membuat alur test lurus dan pesan galat asli ikut terlaporkan. Gunakan assert! beserta kerabatnya ketika kita membandingkan nilai konkret, karena laporan left/right dari assert_eq! jauh lebih kaya daripada satu baris Error:. Untuk fungsi yang menolak input dengan panic, tetap gunakan #[should_panic] — attribute tersebut berpasangan dengan test bertipe (), bukan Result.
Ringkasan #
Fungsi test boleh mengembalikan Result<(), String>: Ok(()) berarti lulus, Err berarti gagal dengan pesannya tercetak pada baris Error:. Operator ? bekerja penuh di dalam test semacam ini, membuat pemanggilan fungsi fallible berantai terasa seperti kode produksi biasa. Kegagalan gaya Result tidak memuat lokasi baris — informasi yang harus kita cari sendiri dari isi test. Dengan semua gaya unit test sudah di tangan, saatnya melangkah keluar dari crate: integration test menguji kode kita sebagaimana pengguna luar memakainya, dari direktori tests yang terpisah.