Unit test yang kita tulis sejauh ini hidup di dalam crate, menempel pada kode produksi, dan bebas menguji fungsi privat sekalipun. Posisi yang nyaman untuk pengembang — namun pengguna nyata crate kita tidak pernah berada di posisi itu. Pengguna hanya melihat apa yang kita beri pub: mereka use nama crate kita, memanggil fungsi publik, dan berhenti di situ. Integration test menempatkan diri persis di posisi pengguna: kode test berada di luar crate, tidak bisa melihat detail internal, dan mengonsumsi antarmuka publik sebagaimana program lain melakukannya. Rust menyiapkan tempat khusus untuk posisi ini — direktori tests di akar proyek — dan cargo test menjalankan isinya sebagai test suite terpisah setelah unit test selesai. Chapter ini membangun crate toko dengan unit test dan integration test berdampingan, membaca laporan dua suite tersebut, lalu membuktikan batas visibilitasnya dengan galat kompilator yang sebenarnya.
Mekanisme: Direktori tests dan Crate Terpisah #
Ketika cargo test berjalan, cargo memeriksa isi direktori tests/ di akar proyek. Setiap file .rs di dalamnya diperlakukan sebagai crate tersendiri yang dikompilasi terpisah dan ditautkan ke pustaka kita — persis seperti crate eksternal mana pun di ekosistem Rust. Konsekuensi mekanisme ini menentukan seluruh karakter integration test: kode di tests/ wajib mengimpor item kita lewat use nama_crate::item, hanya item pub yang terlihat, dan kode bertanda #[cfg(test)] di dalam crate tidak pernah ikut — modul itu memang tidak dikompilasi untuk konsumen luar.
Karena berada di puncak file tanpa modul pembungkus, test integration dilaporkan dengan nama polosnya saja — tanpa prefiks tests:: seperti pada unit test. Setiap file di tests/ juga membentuk binary test tersendiri, sehingga laporan cargo test menampilkan blok running N tests yang terpisah untuk tiap file.
Menyiapkan Crate Toko dengan Dua Suite #
Buat proyek dengan cargo new toko --lib, lalu isi src/lib.rs dengan dua fungsi publik dan satu unit test:
pub fn hitung_diskon(harga: u64, persen: u64) -> u64 {
harga * persen / 100
}
pub fn harga_setelah_diskon(harga: u64, persen: u64) -> u64 {
harga - hitung_diskon(harga, persen)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hitung_diskon_dua_puluh_persen() {
assert_eq!(hitung_diskon(200_000, 20), 40_000);
}
}Selanjutnya buat file tests/integration_test.rs — cargo mengenali direktori tests secara otomatis tanpa konfigurasi tambahan:
use toko::harga_setelah_diskon;
#[test]
fn harga_akhir_sesuai_iklan() {
assert_eq!(harga_setelah_diskon(200_000, 20), 160_000);
}
#[test]
fn tanpa_diskon_harga_tetap() {
assert_eq!(harga_setelah_diskon(150_000, 0), 150_000);
}Jalankan seluruh test dari akar proyek:
cargo testOutput yang Dihasilkan:
Compiling toko v0.1.0 (/tmp/w4e-integration-test/toko)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.22s
Running unittests src/lib.rs (target/debug/deps/toko-437cac73a95d3df8)
running 1 test
test tests::hitung_diskon_dua_puluh_persen ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running tests/integration_test.rs (target/debug/deps/integration_test-3e98513de2f3ed7b)
running 2 tests
test tanpa_diskon_harga_tetap ... ok
test harga_akhir_sesuai_iklan ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests toko
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00sLaporan di atas memuat dua blok running N tests — itulah tanda dua suite berjalan. Mari kita bedah kode dan laporannya:
- Baris 1-3 (
pub fn hitung_diskon(...)): fungsi pertama diberipubagar terlihat dari luar. Unit test mengujinya langsung dari dalam crate. - Baris 5-7 (
pub fn harga_setelah_diskon(...)): fungsi kedua memanggil fungsi pertama — pengguna luar cukup memakai fungsi ini tanpa pedulihitung_diskonada di baliknya. - Baris 9-17 (
#[cfg(test)] mod tests): unit test untukhitung_diskontetap disrc/lib.rs, tempat detail internal diuji. Laporan blok pertamaRunning unittests src/lib.rsberasal dari suite ini, dan namanya tampil dengan prefikstests::. - Baris 1 file
tests/integration_test.rs(use toko::harga_setelah_diskon;): import lewat nama cratetoko, bukancrate::atausuper::. Posisi file ini memang di luar crate, sehingga jalur masuknya sama seperti jalur pengguna eksternal. - Baris 3-6 dan 8-11 file
tests/integration_test.rs: dua test memakaiassert_eq!seperti biasa. Perhatikan laporan blok kedua: namanya polos (tanpa_diskon_harga_tetap,harga_akhir_sesuai_iklan) tanpa prefiks modul, karena test ini berada di akar file, bukan dalam modultests. - Baris
Running tests/integration_test.rs (...): judul blok kedua menunjukkan asal file, dan binary-nya berbeda dari suite unit —integration_test-3e98513de2f3ed7b, lengkap dengan hash build-nya sendiri.
Menjalankan Hanya Suite Integration #
Saat iterasi cepat, kita bisa memilih suite mana yang dijalankan dengan opsi --test diikuti nama file tanpa akhiran .rs:
cargo test --test integration_testOutput yang Dihasilkan:
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.00s
Running tests/integration_test.rs (target/debug/deps/integration_test-3e98513de2f3ed7b)
running 2 tests
test harga_akhir_sesuai_iklan ... ok
test tanpa_diskon_harga_tetap ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00sHanya blok integration yang berjalan; unit test di src/lib.rs tidak ikut dieksekusi. Sebaliknya, cargo test --lib menjalankan hanya unit test — opsi yang sudah kita lihat tercetak pada laporan kegagalan Chapter-Chapter sebelumnya. Penyaringan berlapis seperti ini membuat siklus edit-tes pada proyek besar tetap ringkas.
Batas Visibilitas: Hanya Item Publik #
Sekarang bukti bahwa integration test benar-benar berdiri di luar. Kita ubah hitung_diskon menjadi privat — hilangkan pub-nya — lalu integration test mencoba mengimpornya:
fn hitung_diskon(harga: u64, persen: u64) -> u64 {
harga * persen / 100
}
pub fn harga_setelah_diskon(harga: u64, persen: u64) -> u64 {
harga - hitung_diskon(harga, persen)
}File tests/integration_test.rs diganti isinya:
use toko::hitung_diskon;
#[test]
fn hitung_diskon_dari_luar_crate() {
assert_eq!(hitung_diskon(200_000, 20), 40_000);
}Output yang Dihasilkan:
Compiling toko v0.1.0 (/tmp/w4e-integration-test/toko)
error[E0603]: function `hitung_diskon` is private
--> tests/integration_test.rs:1:11
|
1 | use toko::hitung_diskon;
| ^^^^^^^^^^^^^ private function
|
note: the function `hitung_diskon` is defined here
--> src/lib.rs:1:1
|
1 | fn hitung_diskon(harga: u64, persen: u64) -> u64 {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
For more information about this error, try `rustc --explain E0603`.
error: could not compile `toko` (test "integration_test") due to 1 previous error
warning: build failed, waiting for other jobs to finish...Galat E0603 menolak import fungsi privat — aturan visibilitas dari Section Modul dan Visibilitas bekerja penuh di sini. Bandingkan dengan posisi unit test di Chapter Unit Test dan mod tests: mod tests di dalam crate bisa menguji fungsi privat, sedangkan file di tests/ tidak. Perbedaan ini bukan kelemahan melainkan desain: integration test sengaja dibutakan terhadap internal agar lulusnya test benar-benar berarti antarmuka publik kita bekerja, bukan sekadar detail dalam yang kebetulan benar. Bila sebuah fungsi internal layak diuji dari luar, pertimbangkan apakah fungsi tersebut memang seharusnya menjadi bagian API publik.
Ringkasan #
Integration test menempati direktori tests/ di akar proyek; setiap file di dalamnya dikompilasi sebagai crate terpisah yang mengonsumsi antarmuka pub kita lewat use nama_crate::item. Laporan cargo test menampilkan suite unit terlebih dahulu lalu tiap file integration sebagai blok running N tests tersendiri, dan opsi --test <nama> menyaring suite yang dijalankan. Hanya item publik yang terlihat dari tests/ — pelanggarannya ditolak dengan E0603, menegaskan bahwa integration test menguji dari kursi pengguna. Masih satu jenis test tersisa di cargo, yang bahkan tidak meminta kita menulis file test terpisah: contoh kode di dokumentasi. Chapter penutup section ini membahas doc test.