Sepanjang dua Chapter sebelumnya kita menulis assert_eq! beberapa kali tanpa membedahnya, padahal macro ini adalah alat assertion yang paling sering dipakai dalam test Rust sehari-hari. Pola assert!(hasil == harapan) memang bisa mewakili perbandingan, namun ketika perbandingan itu gagal, assert! hanya mencetak bahwa kondisinya salah — angka berapa yang tidak cocok tidak ditampilkan. Macro assert_eq! hadir untuk kebutuhan tersebut: macro ini membandingkan dua nilai dan, saat keduanya berbeda, melaporkan kedua nilai tersebut pada dua sisi bernama left dan right. Kita juga akan berkenalan dengan saudaranya assert_ne! yang menegaskan dua nilai tidak sama, serta satu konsekuensi teknis penting: kedua macro meminta tipe yang dibandingkan mengimplementasikan trait PartialEq untuk perbandingannya dan trait Debug untuk pencetakan laporannya. Chapter ini memakai domain konversi suhu agar contoh nilainya mudah diperiksa secara mental.

Cara Kerja assert_eq! di Balik Layar #

Secara konsep, assert_eq!(a, b) adalah perluasan dari assert!(a == b). Saat kompilator mengembangkan macro ini, dua pemeriksaan bekerja berurutan. Pertama, nilai a dan b dibandingkan dengan operator == — karena itu tipe kedua argumen wajib mengimplementasikan trait PartialEq, syarat yang sudah kita kenal di Section Struktur Data. Kedua, apabila perbandingan menghasilkan false, macro memanggil panic! dengan muatan yang dibangun lewat formatting {:?}: nilai a dicetak pada sisi left, nilai b dicetak pada sisi right. Pencetakan {:?} inilah alasan tipe tersebut juga wajib mengimplementasikan Debug.

Konsekuensi praktisnya: pesan kegagalan assert_eq! selalu memuat kedua nilai yang berseteru. Kita tidak perlu lagi menulis pesan kustom hanya untuk mengetahui nilai aktualnya — nilainya sudah hadir di laporan secara gratis. Pesan kustom tetap bisa ditambahkan sebagai argumen ketiga dan akan tercetak setelah label left dan right, mengikuti aturan format string yang sama dengan assert!.

Membandingkan Hasil Konversi Suhu #

Buat proyek baru dengan cargo new suhu --lib, lalu isi src/lib.rs dengan fungsi konversi suhu, satu struct pembacaan suhu, dan lima test:

rust
#[derive(Debug, PartialEq)]
pub struct Suhu {
    pub celsius: i64,
}

pub fn celsius_ke_fahrenheit(celsius: i64) -> i64 {
    celsius * 9 / 5 + 32
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn titik_beku_air() {
        assert_eq!(celsius_ke_fahrenheit(0), 32);
    }

    #[test]
    fn titik_didih_air() {
        assert_eq!(celsius_ke_fahrenheit(100), 212);
    }

    #[test]
    fn suhu_tubuh_normal() {
        assert_eq!(celsius_ke_fahrenheit(37), 98);
    }

    #[test]
    fn dua_pembacaan_yang_sama() {
        assert_eq!(Suhu { celsius: 25 }, Suhu { celsius: 25 });
    }

    #[test]
    fn dua_pembacaan_yang_berbeda() {
        assert_ne!(Suhu { celsius: 25 }, Suhu { celsius: 31 });
    }
}

Jalankan test:

bash
cargo test

Output yang Dihasilkan:

text
   Compiling suhu v0.1.0 (/tmp/w4e-assert-eq-dan-assert-ne/suhu)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.15s
     Running unittests src/lib.rs (target/debug/deps/suhu-e07e2f2098d3ad03)

running 5 tests
test tests::dua_pembacaan_yang_sama ... ok
test tests::dua_pembacaan_yang_berbeda ... ok
test tests::titik_beku_air ... ok
test tests::suhu_tubuh_normal ... ok
test tests::titik_didih_air ... ok

test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests suhu

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Mari kita bedah kode di atas:

  1. Baris 1 (#[derive(Debug, PartialEq)]): kedua trait yang dibutuhkan assertion diperoleh sekaligus lewat derive macro — PartialEq mengaktifkan operator ==, Debug mengaktifkan pencetakan {:?} pada laporan kegagalan. Tanpa salah satunya, kode test di bawahnya tidak akan lolos kompilasi.
  2. Baris 6 (pub fn celsius_ke_fahrenheit(celsius: i64) -> i64): fungsi konversi dengan aritmetika bilangan bulat; 37 * 9 / 5 + 32 menghasilkan 98 (pembulatan ke bawah pada pembagian bulat).
  3. Baris 14 (assert_eq!(celsius_ke_fahrenheit(0), 32);): pola penempatan baku yang perlu kita pegang — hasil aktual dari pemanggilan fungsi di sisi kiri, nilai harapan di sisi kanan. Penempatan konsisten ini membuat laporan left dan right selalu mudah ditafsirkan.
  4. Baris 26 (assert_eq!(Suhu { celsius: 25 }, Suhu { celsius: 25 });): assert_eq! tidak terbatas pada angka; dua instance struct dibandingkan field demi field berkat PartialEq hasil derive.
  5. Baris 31 (assert_ne!(Suhu { celsius: 25 }, Suhu { celsius: 31 });): kebalikan perbandingan — test lulus justru ketika kedua nilai berbeda. assert_ne! berguna untuk menegaskan bahwa dua hal yang memang seharusnya tidak tertukar benar-benar berbeda.

Saat Gagal: Membaca left dan right #

Sekarang kita sengaja gagalkan satu test untuk melihat bentuk laporannya. Test berikut mengharapkan suhu tubuh manusia normal setara dengan 100 derajat Fahrenheit — angka yang salah:

rust
#[test]
fn suhu_tubuh_normal_dalam_fahrenheit() {
    assert_eq!(celsius_ke_fahrenheit(37), 100, "37 derajat Celsius seharusnya {} derajat Fahrenheit", 98);
}

Output yang Dihasilkan:

text
   Compiling suhu v0.1.0 (/tmp/w4e-assert-eq-dan-assert-ne/suhu)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.09s
     Running unittests src/lib.rs (target/debug/deps/suhu-e07e2f2098d3ad03)

running 1 test
test tests::suhu_tubuh_normal_dalam_fahrenheit ... FAILED

failures:

---- tests::suhu_tubuh_normal_dalam_fahrenheit stdout ----

thread 'tests::suhu_tubuh_normal_dalam_fahrenheit' (16038) panicked at src/lib.rs:16:9:
assertion `left == right` failed: 37 derajat Celsius seharusnya 98 derajat Fahrenheit
  left: 98
 right: 100
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    tests::suhu_tubuh_normal_dalam_fahrenheit

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`

Test di atas ditempatkan menggantikan kelima test sebelumnya di dalam mod tests pada src/lib.rs yang sama, sehingga pemanggilan assert_eq! jatuh pada baris ke-16 file lengkap — itulah asal lokasi src/lib.rs:16:9 pada laporan. Cara membaca laporan ini adalah kunci bab. Baris left: 98 adalah argumen pertama — hasil aktual celsius_ke_fahrenheit(37). Baris right: 100 adalah argumen kedua — nilai harapan yang kita tulis. Ketimpangan 98 lawan 100 langsung memperlihatkan pihak mana yang keliru: fungsi menghitung benar, harapan kita yang salah — berbeda dengan kasus Chapter Filosofi Testing di mana left yang menampakkan hasil salah. Pesan kustom tercetak setelah frasa assertion \left == right` failed` dan sebelum kedua nilai, sehingga satu laporan memuat konteks sekaligus buktinya.

Perbandingan Struct dan Peran Debug #

Ketika perbandingan struct gagal, jasa Debug baru terasa. Dua pembacaan suhu yang berbeda sengaja kita samakan. Dua cuplikan berikut dijalankan pada src/lib.rs yang berisi struct Suhu di bagian atas dan satu test ini di dalam mod tests, sehingga assertion berada di baris ke-12:

rust
#[test]
fn suhu_ruang_sama_dengan_suhu_luar() {
    assert_eq!(Suhu { celsius: 22 }, Suhu { celsius: 31 });
}

Output yang Dihasilkan:

text
thread 'tests::suhu_ruang_sama_dengan_suhu_luar' (16192) panicked at src/lib.rs:12:9:
assertion `left == right` failed
  left: Suhu { celsius: 22 }
 right: Suhu { celsius: 31 }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Kedua nilai tampil utuh sebagai Suhu { celsius: 22 } dan Suhu { celsius: 31 } — itulah format keluaran {:?} milik Debug. Kita bisa langsung melihat field mana yang berbeda tanpa membuka kode. Bandingkan dengan situasi bila derive(Debug) dihapus dari struct: perbandingan masih sah karena PartialEq ada, namun kompilator menolak kode test-nya.

rust
#[derive(PartialEq)]
pub struct Suhu {
    pub celsius: i64,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn suhu_ruang_sama_dengan_suhu_luar() {
        assert_eq!(Suhu { celsius: 22 }, Suhu { celsius: 22 });
    }
}

Output yang Dihasilkan:

text
   Compiling suhu v0.1.0 (/tmp/w4e-assert-eq-dan-assert-ne/suhu)
error[E0277]: `Suhu` doesn't implement `Debug`
  --> src/lib.rs:12:9
   |
12 |         assert_eq!(Suhu { celsius: 22 }, Suhu { celsius: 22 });
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Debug` is not implemented for `Suhu`
   |
help: consider annotating `Suhu` with `#[derive(Debug)]`
   |
 2 + #[derive(Debug)]
 3 | pub struct Suhu {
   |

For more information about this error, try `rustc --explain E0277`.
error: could not compile `suhu` (lib test) due to 2 previous errors

Galat E0277 menegaskan syarat Debug itu nyata, dan kompilator bahkan menyodorkan perbaikannya: tambahkan #[derive(Debug)] pada struct. Catatan menarik pada laporan terakhir: meski hanya satu blok galat yang tampil, baris penutup menyebut 2 previous errors — kedua argumen assert_eq! sama-sama melanggar syarat Debug, dan rustc menggabungkan keduanya menjadi satu tampilan.

Ketika assert_ne! Gagal #

assert_ne! gagal ketika kedua nilai justru sama, dan laporannya memakai label yang sesuai. Cuplikan berikut juga berada di baris ke-12 dari src/lib.rs dengan struct Suhu di atasnya:

rust
#[test]
fn suhu_ruang_tidak_sama_dengan_suhu_luar() {
    assert_ne!(Suhu { celsius: 25 }, Suhu { celsius: 25 });
}

Output yang Dihasilkan:

text
thread 'tests::suhu_ruang_tidak_sama_dengan_suhu_luar' (16396) panicked at src/lib.rs:12:9:
assertion `left != right` failed
  left: Suhu { celsius: 25 }
 right: Suhu { celsius: 25 }

Frasa left != right menandakan arah ekspektasi yang dibalik: kita menuntut perbedaan, namun kenyataannya left dan right identik. Nilai yang sama pada kedua sisi adalah bukti bahwa penegasan ketidaksamaan kita terlanggar.

Ringkasan #

assert_eq!(aktual, harapan) membandingkan dua nilai lewat PartialEq dan mencetak keduanya pada sisi left dan right lewat Debug ketika gagal; assert_ne! membalik arah ekspektasi dengan laporan left != right. Konvensi menempatkan hasil aktual di kiri dan harapan di kanan menjaga laporan kegagalan mudah ditafsirkan. Tipe buatan sendiri tinggal diberi #[derive(Debug, PartialEq)] agar siap dibandingkan. Hingga titik ini seluruh test kita hanya menuntut kondisi benar atau nilai sama; bagaimana menguji fungsi yang justru harus panic? Chapter berikutnya menjawabnya dengan #[should_panic].