Doc Test

Contoh dokumentasi yang wajib dapat dikompilasi dan lulus.

Daftar isi

Topik ini membahas doc test dari sisi pengujian, yaitu bagaimana contoh pada komentar dokumentasi menjadi bagian tetap rangkaian cargo test alih-alih sekadar ilustrasi yang diharapkan tetap benar. Pembahasan mencakup posisi doc test di antara unit test dan integration test, skema penamaan yang dipakai kerangka pengujian, penyaringan doc test tertentu, cara membaca laporan kegagalannya, serta dua mekanisme penyusunan contoh yang berdampak langsung pada pengujian, yakni baris tersembunyi dan anotasi should_panic. Contoh memakai proyek konversi-suhu yang dijalankan sungguhan dengan cargo test. Setelah membacanya, contoh dokumentasi dapat diperlakukan sebagai pengujian biasa, lengkap dengan nama yang dapat dibaca, saringan yang dapat dipakai, dan laporan kegagalan yang dapat dimengerti.

Posisi doc test di dalam rangkaian pengujian#

Doc test melengkapi unit test dan integration test dengan sumber yang berbeda, yaitu blok kode berpagar pada komentar /// dan //!, bukan fungsi bertanda #[test]. rustdoc mengekstrak setiap blok itu, membungkus pernyataannya menjadi program kecil yang mengimpor pustaka layaknya pemakai luar, lalu mengompilasi dan menjalankan program itu selama cargo test. Karena dijalankan sebagai pemakai luar, contoh yang memajang pemakaian yang keliru atau yang usang setelah antarmuka berubah gagal dikompilasi. Kegagalan itu muncul pada saat yang sama dengan kegagalan pengujian lain.

Aspekunit testintegration testdoc test
sumber pengujianfungsi bertanda #[test]fungsi pada berkas tests/blok contoh pada komentar ///
posisi kompilasidi dalam cratecrate terpisahsatu program kecil per contoh
yang diperiksadetail implementasikontrak API publikcontoh pemakaian yang didokumentasikan
penanda pada laporannama fungsinama fungsinama item dan baris pagar contoh

Doc test tidak menggantikan dua jenis lainnya karena keduanya menguji hal yang berbeda. Kasus batas seperti nilai ambang 59 dan 60 milik unit test, sedangkan contoh dokumentasi sebaiknya tetap pendek dan mudah disalin pembaca. Setiap contoh juga dikompilasi sebagai program tersendiri, sehingga variabel, impor, dan use tidak berpindah antar contoh. Kedua sifat itu menjadikan jumlah asersi pada satu contoh tetap wajar dan setiap contoh mandiri.

Proyek contoh dengan tiga bentuk contoh#

Proyek konversi-suhu memuat tiga fungsi konversi suhu untuk catatan laboratorium, dan setiap fungsi membawa satu contoh yang memperagakan keperluan pengujian yang berbeda. Contoh pertama menuliskan impor use secara penuh sehingga tampak seperti kode yang disalin pembaca. Contoh kedua menyembunyikan baris impor dengan awalan # karena baris itu tetap dikompilasi tetapi tidak dirender pada halaman dokumentasi. Contoh ketiga memakai anotasi should_panic karena fungsinya memang berkontrak panic untuk masukan yang tidak sah.

toml Cargo.toml
[package]
name = "konversi-suhu"
version = "0.1.0"
edition = "2021"
rust src/lib.rs
// tidak-dijalankan sendirian oleh rustc karena berkas pustaka tanpa fungsi main.

//! Konversi satuan suhu untuk catatan laboratorium.

/// Mengubah derajat Celsius menjadi Fahrenheit.
///
/// ```
/// use konversi_suhu::celsius_ke_fahrenheit;
/// assert_eq!(celsius_ke_fahrenheit(0), 32);
/// assert_eq!(celsius_ke_fahrenheit(37), 98);
/// ```
pub fn celsius_ke_fahrenheit(c: i32) -> i32 {
    c * 9 / 5 + 32
}

/// Mengubah derajat Fahrenheit menjadi Celsius dengan pembulatan ke bawah.
///
/// ```
/// # use konversi_suhu::fahrenheit_ke_celsius;
/// assert_eq!(fahrenheit_ke_celsius(212), 100);
/// assert_eq!(fahrenheit_ke_celsius(98), 36);
/// ```
pub fn fahrenheit_ke_celsius(f: i32) -> i32 {
    (f - 32) * 5 / 9
}

/// Membaca catatan suhu dari teks, misalnya "37".
///
/// Teks yang bukan angka memicu panic karena dianggap kesalahan program,
/// sehingga contoh berikut menandai kepanikan yang memang diharapkan.
///
/// ```should_panic
/// use konversi_suhu::suhu_dari_teks;
/// suhu_dari_teks("mendidih");
/// ```
pub fn suhu_dari_teks(teks: &str) -> i32 {
    teks.trim().parse().expect("suhu harus berupa angka")
}

Anotasi should_panic pada contoh ketiga bekerja seperti pada unit test, yaitu contoh dianggap lulus justru ketika berakhir dengan panic. Tanpa anotasi itu, contoh yang memperagakan fungsi berkontrak panic akan gagal setiap kali dijalankan. Diagram berikut memetakan perjalanan satu blok contoh dari komentar dokumentasi sampai menjadi pengujian yang dijalankan.

komentar dokumentasi///rustdocmengekstrak blok kodesetiap contohdibungkus fn maindikompilasilalu dijalankancontoh usang gagal dikompilasi
Perjalanan blok contoh dari komentar dokumentasi menjadi pengujian

Eksekusi contoh memakai proyek di luar pohon konten situs, dengan seluruh berkas disalin dari bundle topik lebih dahulu.

bash
# siapkan proyek percobaan yang bersih di luar pohon konten
rm -rf /tmp/proyek-doctest
cargo new /tmp/proyek-doctest --lib -q --vcs none
# salin berkas proyek dari bundle ke posisinya masing-masing
cp Cargo.toml /tmp/proyek-doctest/Cargo.toml
cp src/lib.rs /tmp/proyek-doctest/src/lib.rs

Menjalankan dan menyaring doc test#

Perintah cargo test menjalankan doc test sebagai bagian tersendiri setelah unit test. Pada proyek ini bagian pertama melaporkan nol karena tidak ada fungsi bertanda #[test], sedangkan bagian kedua menjalankan tiga contoh. Segmen waktu pada baris ringkasan dibuang dengan sed karena lama kompilasi setiap contoh berubah pada setiap jalankan.

bash
# jalankan seluruh target uji secara berurutan lalu buang segmen waktu
cargo test --manifest-path /tmp/proyek-doctest/Cargo.toml -q -- --test-threads=1 2>/dev/null | sed 's/; finished in [0-9.]*s$//'
text Keluaran
running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

running 3 tests
...
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

Opsi --doc membatasi perintah pada doc test saja, dan daftar lengkapnya dapat dilihat tanpa menjalankannya dengan meneruskan --list setelah tanda --. Nama setiap doc test memuat tiga bagian, yaitu berkas sumber, nama item tempat contoh ditulis, dan baris pagar pembuka contoh pada berkas sumber.

bash
# tampilkan daftar doc test tanpa menjalankannya
cargo test --manifest-path /tmp/proyek-doctest/Cargo.toml --doc -q -- --list 2>/dev/null
text Keluaran
src/lib.rs - celsius_ke_fahrenheit (line 7): test
src/lib.rs - fahrenheit_ke_celsius (line 18): test
src/lib.rs - suhu_dari_teks (line 32): test

Saringan nama bekerja seperti pada unit test, yaitu hanya doc test yang namanya memuat saringan itu yang dijalankan. Saringan fahrenheit menjalankan dua contoh karena nama celsius_ke_fahrenheit dan fahrenheit_ke_celsius sama-sama memuat kata itu, dan baris ringkasan menghitung satu contoh yang tersaring keluar.

bash
# jalankan hanya doc test yang namanya memuat kata fahrenheit
cargo test --manifest-path /tmp/proyek-doctest/Cargo.toml --doc fahrenheit -q -- --test-threads=1 2>/dev/null | sed 's/; finished in [0-9.]*s$//'
text Keluaran
running 2 tests
..
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out

Anotasi tingkat eksekusi seperti no_run dan compile_fail mengatur apakah contoh ikut dijalankan atau hanya diperiksa kompilasinya, dan pembahasannya berada pada topik penulisan dokumentasi dan contoh. Dari sisi pengujian, seluruh anotasi itu tetap menghasilkan doc test dengan skema penamaan yang sama, sehingga saringan dan daftar nama tetap menjadi alat navigasi utama.

Membaca laporan kegagalan doc test#

Bentuk laporan kegagalan dapat diperiksa dengan sengaja merusak satu asersi pada contoh pertama. Blok berikut mengubah nilai harapan 32 menjadi 31, menjalankan hanya doc test, lalu memulihkan berkas aslinya dari bundle. Nomor thread pada pesan panic disaring karena nilainya berubah pada setiap jalankan.

bash
# ubah sementara satu asersi pada contoh pertama
sed -i 's/assert_eq!(celsius_ke_fahrenheit(0), 32);/assert_eq!(celsius_ke_fahrenheit(0), 31);/' /tmp/proyek-doctest/src/lib.rs
# jalankan hanya doc test lalu saring nomor thread dan segmen waktu
cargo test --manifest-path /tmp/proyek-doctest/Cargo.toml --doc -q -- --test-threads=1 2>/dev/null | sed -E 's/ \([0-9]+\)//; s/; finished in [0-9.]*s$//'
# pulihkan berkas asli dari bundle
cp src/lib.rs /tmp/proyek-doctest/src/lib.rs
text Keluaran
running 3 tests
src/lib.rs - celsius_ke_fahrenheit (line 7) --- FAILED
..
failures:

---- src/lib.rs - celsius_ke_fahrenheit (line 7) stdout ----
Test executable failed (exit status: 101).

stderr:

thread 'main' panicked at src/lib.rs:6:1:
assertion `left == right` failed
  left: 32
 right: 31
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

failures:
    src/lib.rs - celsius_ke_fahrenheit (line 7)

test result: FAILED. 2 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out

Laporan itu memuat empat lapis informasi. Baris FAILED dan daftar di bagian akhir menyebut nama doc test lengkap dengan baris pagarnya, dan nama itulah penunjuk yang andal ke lokasi contoh pada berkas sumber. Bagian stdout melaporkan bahwa program kecil hasil rakitan gagal dengan kode keluar 101, yaitu kode keluar standar Rust ketika main berakhir karena panic, dan baris thread 'main' pada bagian stderr menegaskan bahwa setiap contoh memang berjalan sebagai program tersendiri. Nilai left dan right dibaca seperti pada unit test, sedangkan filtered out tetap menghitung contoh yang tidak dijalankan karena saringan nama.

Kegagalan doc test menghentikan cargo test dengan kode keluar bukan nol, sama seperti kegagalan unit test dan integration test. Perilaku itu membuat doc test menjadi gerbang yang setara di dalam cargo test, sehingga contoh dokumentasi yang usang tidak dapat lolos begitu API berubah.

Kesalahan umum#

Menumpuk banyak asersi pada satu contoh. Contoh yang memuat sepuluh asersi tetap lulus sebagai pengujian tetapi gagal sebagai dokumentasi karena sulit dibaca. Pindahkan pemeriksaan rinci ke unit test, dan biarkan contoh memuat dua sampai tiga asersi yang memperlihatkan hasil khas fungsi.

Mengandalkan nomor baris pada pesan panic. Nomor pada kolom panicked at berasal dari program hasil rakitan, bukan dari berkas sumber, sehingga mencari baris itu pada editor sering menunjuk tempat yang keliru. Gunakan nama doc test yang memuat baris pagar pembuka contoh sebagai titik berangkat perbaikan.

Membiarkan contoh bergantung satu sama lain. Setiap contoh dikompilasi sebagai program tersendiri, sehingga variabel atau impor yang dideklarasikan pada contoh sebelumnya tidak terlihat pada contoh berikutnya. Contoh kedua yang memakai variabel contoh pertama gagal dikompilasi, dan perbaikannya adalah mengulang deklarasi yang diperlukan atau menyembunyikannya dengan awalan #.

Ringkasan#

  • Doc test bersumber dari blok contoh pada komentar ///, dikompilasi satu program kecil per contoh sebagai pemakai luar, dan dijalankan cargo test sebagai bagian keluaran tersendiri.
  • Nama doc test berupa berkas - item (line N) dengan N menunjuk baris pagar pembuka contoh, dan nama itu menjadi penunjuk kegagalan yang paling andal.
  • Opsi --doc beserta saringan nama menjalankan sebagian contoh, dan -- --list menampilkan seluruh nama tanpa menjalankannya.
  • Baris berawalan # tetap dikompilasi meskipun disembunyikan dari render, dan should_panic menjadikan contoh lulus justru ketika panic yang didokumentasikan terjadi.

Referensi#