Topik ini membahas dua mekanisme yang menjaga contoh kode tetap dapat dikompilasi: doc test yang menjalankan blok contoh pada komentar dokumentasi sebagai pengujian, serta penyertaan README ke dalam pengujian yang sama. Pembahasan menyinggung cara rustdoc mengubah contoh menjadi pengujian, anotasi yang mengatur tingkat eksekusinya, dan pola berkas yang membuat contoh pada README ikut diperiksa. Setelah membacanya, pembaca dapat menulis dokumentasi yang gagal membangun ketika contohnya keliru, sehingga dokumentasi tidak lagi berhenti sebagai teks yang meyakinkan tetapi tidak pernah diuji.
Doc test pada komentar dokumentasi#
Doc test adalah pengujian yang bersumber dari contoh pada komentar dokumentasi. Ketika cargo test berjalan, rustdoc mengumpulkan setiap blok kode berpagar pada komentar /// dan //!. Setiap blok kemudian dibungkus menjadi sebuah program kecil, dikompilasi sebagai crate terpisah yang memakai pustaka layaknya pengguna luar, lalu dijalankan. Contoh yang gagal dikompilasi atau gagal asersinya membuat pengujian gagal, sehingga dokumentasi tidak dapat memajang pemakaian yang keliru. Karena kompilasi dilakukan sebagai pengguna luar, item pustaka harus diimpor dengan use sebagaimana pada kode biasa.
Proyek contoh bernama penghitung-kata terdiri atas tiga berkas. Berkas Cargo.toml menyatakan identitas paket, src/lib.rs memuat fungsi hitung_kata dengan contoh pada bagian # Contoh dokumentasinya, dan README.md menampilkan contoh pemakaian bagi pengunjung repositori. Tiga baris setelah komentar //! pada pustaka menyertakan README ke dalam pengujian lewat pola yang diuraikan pada bagian README yang ikut diuji.
[package]
name = "penghitung-kata"
version = "0.1.0"
edition = "2021"// tidak-dijalankan sendirian oleh rustc karena berkas ini bagian pustaka tanpa fungsi main.
//! Penghitung kata untuk teks bahasa Indonesia.
#[cfg(doctest)]
#[doc = include_str!("../README.md")]
struct ReadmeDoctests;
/// Menghitung jumlah kata pada sebuah teks.
///
/// Kata dipisahkan oleh spasi kosong sehingga tanda baca tetap dihitung
/// sebagai bagian dari kata.
///
/// # Contoh
///
/// ```
/// use penghitung_kata::hitung_kata;
///
/// assert_eq!(hitung_kata("beras gula kopi"), 3);
/// assert_eq!(hitung_kata(""), 0);
/// ```
pub fn hitung_kata(teks: &str) -> usize {
teks.split_whitespace().count()
}# penghitung-kata
Pustaka kecil untuk menghitung kata pada teks bahasa Indonesia.
Contoh pemakaian:
```rust
use penghitung_kata::hitung_kata;
let jumlah = hitung_kata("beras gula kopi");
assert_eq!(jumlah, 3);
```Contoh pada src/lib.rs memanggil fungsi dua kali dengan masukan yang mudah diverifikasi, yaitu tiga kata dan teks kosong, dan dua pemanggilan assert_eq menjadi kriteria lulus pengujian. Contoh pada README memakai bentuk yang sama tetapi menyimpan hasilnya ke variabel lebih dahulu, meniru gaya tulis pengguna nyata. Verifikasi dilakukan pada proyek di luar pohon konten, dan segmen waktu pada baris rangkuman disaring dengan sed karena nilainya berubah setiap kali pengujian dijalankan.
# siapkan proyek pustaka yang bersih di luar pohon konten
rm -rf /tmp/proyek-doc
cargo new /tmp/proyek-doc --lib -q --vcs none
# salin berkas proyek dari bundle ke posisinya masing-masing
cp Cargo.toml /tmp/proyek-doc/Cargo.toml
cp src/lib.rs /tmp/proyek-doc/src/lib.rs
cp README.md /tmp/proyek-doc/README.md
# jalankan seluruh pengujian lalu buang segmen waktu yang berubah setiap kali dijalankan
cargo test --manifest-path /tmp/proyek-doc/Cargo.toml -q 2>&1 | sed 's/; finished in [0-9.]*s$//'running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
running 2 tests
..
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered outKeluaran memuat dua harness pengujian. Harness pertama menjalankan unit test pada kode dan melaporkan nol karena pustaka belum memuat fungsi bertanda #[test]. Harness kedua menjalankan doc test, dan kedua titik menandakan dua contoh yang lulus tanpa keluaran. Daftar lengkapnya dapat dilihat tanpa menjalankan pengujian.
# tampilkan daftar doc test yang dikumpulkan tanpa menjalankannya
cargo test --manifest-path /tmp/proyek-doc/Cargo.toml --doc -q -- --listsrc/lib.rs - ReadmeDoctests (line 12): test
src/lib.rs - hitung_kata (line 16): testNama setiap doc test mencantumkan berkas serta item tempat contoh berada. Nama pertama menunjuk struct ReadmeDoctests yang menjadi wadah contoh dari README, sedangkan nama kedua menunjuk fungsi hitung_kata tempat komentar # Contoh ditulis. Opsi --doc membatasi perintah pada doc test saja, dan --list setelah tanda -- diteruskan ke runner pengujian sebagai permintaan daftar.
Anotasi tingkat eksekusi#
Tidak semua contoh layak dijalankan pada saat pengujian. Contoh yang membaca berkas milik pengguna, membuka jaringan, atau memakan waktu lama tetap boleh ditampilkan asalkan kompilasinya diperiksa. Anotasi pada blok contoh mengatur tingkat eksekusinya, dan anotasinya ditulis menempel pada pagar pembuka, misalnya no_run.
| Anotasi | Dikompilasi | Dijalankan |
|---|---|---|
| tanpa anotasi | ya | ya |
no_run | ya | tidak |
ignore | tidak | tidak |
should_panic | ya | ya, wajib panic |
compile_fail | tidak, wajib gagal | tidak |
Pernyataan contoh yang ditulis tanpa fungsi dibungkus rustdoc ke dalam fn main bawaan yang mengembalikan (). Operator ? hanya dapat dipakai pada fungsi yang mengembalikan Result, sehingga contoh yang memakai operator tersebut perlu fungsi main eksplisit dengan tipe kembalian Result. Bentuk berikut menggambarkan fungsi pembaca berkas pada sebuah pustaka: anotasi no_run menjaga contoh tetap dikompilasi, sementara main eksplisit mengizinkan pemakaian ?.
// tidak-dijalankan
/// Membaca daftar belanja dari sebuah berkas teks.
///
/// ```no_run
/// use penghitung_kata::muat_daftar;
///
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let daftar = muat_daftar("daftar.txt")?;
/// println!("{daftar}");
/// Ok(())
/// }
/// ```
pub fn muat_daftar(_nama: &str) -> Result<String, std::io::Error> {
Ok(String::new())
}Dua kemampuan pelengkap menuntaskan penulisan contoh. Baris di dalam blok contoh yang diawali karakter # tetap dikompilasi tetapi tidak dirender pada halaman dokumentasi, sehingga impor atau kode persiapan dapat disembunyikan tanpa membuat contoh gagal membangun. Anotasi should_panic menjalankan contoh dan menganggapnya lulus bila contoh berakhir dengan panic, sedangkan compile_fail mengharapkan kompilasi gagal sehingga cocok untuk memajang pesan error yang memang dimaksudkan pengguna.
README yang ikut diuji#
Tiga baris pada src/lib.rs membentuk satu pola yang utuh. Makro include_str! membaca README.md pada waktu kompilasi dan menghasilkan string berisi seluruh isinya, lalu atribut #[doc = ...] menjadikan string itu komentar dokumentasi bagi struct ReadmeDoctests yang berada tepat di bawahnya. rustdoc kemudian mengumpulkan blok kode rust pada komentar tersebut sebagai doc test, dan contoh pemakaian pada README pun diperiksa dengan mekanisme yang sama seperti contoh pada ///.
Dua detail membuat pola ini aman dipasang. Penanda #[cfg(doctest)] membuat struct hanya ada ketika rustdoc menyusun doc test, sehingga pembangunan biasa tidak melihat struct tak terpakai dan tidak memunculkan peringatan kode mati. Struct juga sengaja dibuat privat sehingga tidak pernah menjadi bagian antarmuka publik pustaka. Selain itu, jalur ../README.md dibaca relatif terhadap src/lib.rs karena makro include_str! selalu menyelesaikan jalur dari berkas yang memuatnya.
Pola ini sekaligus menjaga isi README tetap mutakhir. Ketika antarmuka pustaka berubah, contoh pada README yang belum diperbarui gagal dikompilasi pada cargo test, dan kegagalan itu muncul pada saat yang sama dengan kegagalan pengujian lain alih-alih baru ditemukan oleh pengguna pertama yang menyalin contoh basi. README yang tadinya hanya teks deskriptif kini menjadi berkas yang ikut diverifikasi rantai alat.
Kesalahan umum#
Lupa use pada contoh dokumentasi. Doc test dikompilasi sebagai pengguna luar, sehingga pemanggilan hitung_kata tanpa use penghitung_kata::hitung_kata berhenti dengan error yang menyatakan fungsi tersebut tidak ditemukan pada lingkup. Tambahkan impor pada baris pertama contoh, atau sembunyikan impor itu dengan awalan # bila halaman dokumentasi ingin tetap ringkas.
Menulis jalur include_str yang keliru. Makro include_str! membaca jalur relatif terhadap berkas yang memuatnya, sehingga README berada pada ../README.md bila dibaca dari src/lib.rs. Penulisan README.md menghasilkan error yang menyatakan src/README.md tidak dapat dibaca, dan error itu baru muncul saat cargo test karena penanda #[cfg(doctest)] menunda evaluasi baris tersebut hingga penyusunan doc test. Gunakan jalur relatif yang benar dari posisi berkas sumber.
Menjalankan contoh yang membutuhkan lingkungan luar. Blok tanpa anotasi dijalankan penuh saat pengujian, sehingga contoh yang membaca berkas atau jaringan membuat pengujian gagal pada mesin tanpa sumber daya tersebut. Tambahkan no_run bila contoh tetap layak dikompilasi, atau tulis ulang contoh agar mandiri dengan masukan yang tertanam pada teksnya.
Ringkasan#
- Doc test mengubah blok contoh pada komentar
///dan//!menjadi pengujian yang dikompilasi dan dijalankancargo test, dan contoh dikompilasi sebagai pengguna luar sehingga imporusewajib. - Anotasi
no_run,ignore,should_panic, dancompile_failmengatur tingkat eksekusi contoh, sedangkan baris berawalan#disembunyikan dari render tetapi tetap dikompilasi. - Pola
#[cfg(doctest)]dengan#[doc = include_str!("../README.md")]menjadikan contoh pada README sebagai doc test tanpa memengaruhi pembangunan biasa. - Operator
?pada contoh memerlukan fungsimaineksplisit yang mengembalikanResultkarena pembungkus bawaan rustdoc mengembalikan().
Referensi#
- Documentation tests — The rustdoc book — Mekanisme doc test, anotasi tingkat eksekusi, dan baris tersembunyi.
- The #[doc] attribute — The rustdoc book
— Atribut
#[doc = ...]beserta pemakaianinclude_str!. - Documentation — The Rust Book — Bab publikasi yang membahas penulisan komentar dokumentasi bermanfaat beserta contohnya.