Rust'ta test, #[test] özniteliğiyle işaretlenmiş sıradan bir fonksiyondur. Fonksiyon panik yapmadan biterse test geçer, panik yaparsa başarısız olur. Testleri cargo test komutuyla çalıştırırsınız: Cargo kodunuzu test kipinde derler, işaretli fonksiyonları çalıştıran bir test ikilisi üretir ve sonuçları raporlar. Kontroller için assert!, assert_eq! ve assert_ne! makrolarını, panik beklenen durumlar için #[should_panic], yavaş testler için #[ignore] özniteliğini kullanırsınız. Birim testleri kodla aynı dosyada #[cfg(test)] modülünde, entegrasyon testleri tests/ klasöründe, dokümantasyon testleri de /// yorumlarındaki kod bloklarında yaşar.
Bu yazıda bu üç test türünü küçük bir not hesaplama kütüphanesi üzerinde gösteriyor, test çıktısını okumayı, cargo test komutunun önemli bayraklarını ve paralel çalışmanın getirdiği tuzakları The Rust Book, Reference ve The Cargo Book'a dayanarak anlatıyoruz. Proje cargo new --vcs none --lib not_hesap ile oluşturuldu ve tüm komutlar Cargo 1.99.1 ile ağ erişimi olmadan --offline çalıştırıldı. Cargo.toml ve profiller için Cargo.toml ve bağımlılıklar, serinin tamamı için Rust öğrenme rehberi sayfasına bakabilirsiniz.
Bir test fonksiyonunun anatomisi

Kitap, bir test fonksiyonunun gövdesinin genellikle üç iş yaptığını söyler: gereken veriyi ya da durumu hazırlar, test edilen kodu çalıştırır ve sonucun beklendiği gibi olduğunu doğrular. cargo new --lib ile oluşturduğunuz her kütüphanede Cargo, şablon olarak bir add fonksiyonu ve bir tests modülü üretir. Aşağıdaki src/lib.rs dosyası bu şablonun genişletilmiş hali; kütüphane fonksiyonlarını ve birim testlerini birlikte içeriyor.
/// Not ortalamasını hesaplar; boş liste için `None` döner.
///
/// ```
/// assert_eq!(not_hesap::ortalama(&[70, 80, 90]), Some(80));
/// assert_eq!(not_hesap::ortalama(&[]), None);
/// ```
pub fn ortalama(notlar: &[u32]) -> Option<u32> {
if notlar.is_empty() {
return None;
}
Some(notlar.iter().sum::<u32>() / notlar.len() as u32)
}
/// 0-100 arasındaki notu harfe çevirir.
pub fn harf(puan: u32) -> char {
if puan > 100 {
panic!("not 0 ile 100 arasında olmalı, gelen: {puan}");
}
match yuvarla(puan) {
85.. => 'A',
70..=84 => 'B',
50..=69 => 'C',
_ => 'F',
}
}
// pub olmayan yardımcı fonksiyon: birim testleri buna da erişebilir.
fn yuvarla(puan: u32) -> u32 {
(puan + 2) / 5 * 5
}
pub fn puan_oku(metin: &str) -> Result<u32, std::num::ParseIntError> {
metin.trim().parse()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ortalama_dogru() {
assert_eq!(ortalama(&[50, 60]), Some(55));
}
#[test]
fn harf_sinirlari() {
assert_eq!(harf(83), 'A', "83 yuvarlanınca 85 olmalı");
assert_ne!(harf(44), 'C');
assert!(harf(70) == 'B');
}
#[test]
fn ozel_fonksiyon() {
assert_eq!(yuvarla(67), 65);
}
#[test]
#[should_panic(expected = "0 ile 100 arasında")]
fn gecersiz_not() {
harf(101);
}
#[test]
fn metinden_okuma() -> Result<(), std::num::ParseIntError> {
let p = puan_oku(" 75 ")?;
assert_eq!(p, 75);
Ok(())
}
#[test]
#[ignore = "uzun sürüyor"]
fn buyuk_veri() {
let v: Vec<u32> = (0..=100).cycle().take(101_000).collect();
assert_eq!(ortalama(&v), Some(50));
}
}Reference'a göre #[test] yalnızca argüman almayan, jenerik olmayan serbest fonksiyonlara uygulanabilir ve dönüş tipi Termination trait'ini uygulamalıdır. () döndüren testler panik yapmadan biterse, Result<(), E> döndürenler Ok(()) verirse geçer. Test fonksiyonları yalnızca test kipinde derlenir; bu kip rustc --test ya da cargo test ile açılır ve aynı zamanda test yapılandırma seçeneğini etkinleştirir. #[cfg(test)] özniteliği de tam olarak bunu kullanır: Kitabın açıkladığı gibi bu modül cargo build sırasında derlenmez, böylece hem derleme süresi hem de çıktı boyutu küçülür.
tests modülü sıradan bir iç modüldür ve görünürlük kuralları burada da geçerlidir. use super::*; satırı üst modüldeki her şeyi kapsama alır. Kitap, Rust'ın gizlilik kurallarının özel fonksiyonları test etmeye izin verdiğini vurgular: örnekteki yuvarla fonksiyonu pub değil, ama alt modül atalarının öğelerini kullanabildiği için ozel_fonksiyon testi onu doğrudan çağırabiliyor. Modül ve görünürlük kuralları için Rust modül sistemi yazısına bakın.
assert!, assert_eq! ve assert_ne!
assert! bir boolean ifade alır; false ise panic! çağırır. assert_eq! ve assert_ne! iki değeri == ve != ile karşılaştırır ve başarısız olduklarında iki değeri de yazdırır; assert!(a == b) ise yalnızca false sonucu aldığını bildirir. Rust'ta bu argümanlar expected ve actual değil left ve right diye adlandırılır, sıraları da önemli değildir. Değerleri karşılaştırıp yazdırabilmek için tiplerin PartialEq ve Debug uygulaması gerekir; kendi struct ve enum'larınızda bunu genellikle #[derive(PartialEq, Debug)] ile sağlarsınız (Rust derive).
Zorunlu argümanlardan sonra verdiğiniz her şey format! makrosuna aktarılır, böylece başarısızlık mesajına açıklama ekleyebilirsiniz. Aşağıdaki tek dosyalık örnekte bilerek hatalı bir KDV hesabı yazdık; test rustc --test ile derlenip çalıştırıldığında çıktıda hem kendi mesajımız hem de iki değer görünüyor.
fn kdv_ekle(kurus: u64) -> u64 {
kurus * 118 / 100 // hata: oran %20 olmalıydı
}
#[test]
fn kdv_yuzde_yirmi() {
let sonuc = kdv_ekle(1000);
assert_eq!(sonuc, 1200, "1000 kuruşa %20 KDV eklenince {sonuc} çıktı");
}
// rustc --test ile derleyip çalıştırınca çıktıdan seçilmiş satırlar:
// test kdv_yuzde_yirmi ... FAILED
// assertion `left == right` failed: 1000 kuruşa %20 KDV eklenince 1180 çıktı
// left: 1180
// right: 1200
// test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00sÇıktıda bunların yanında thread 'kdv_yuzde_yirmi' (...) panicked at tf.rs:8:5: biçiminde bir satır da bulunur; parantezdeki sayı her çalıştırmada değişir. Kitabın belirttiği gibi her test yeni bir iş parçacığında çalışır ve test iş parçacığı öldüğünde test başarısız sayılır, bu yüzden panik mesajında testin adı iş parçacığı adı olarak görünür. İş parçacığı ve panik ilişkisini Rust thread::spawn yazısında anlattık. Test ikilisi başarısız testte sıfırdan farklı bir kodla çıkar; cargo test sayfasına göre Cargo başarısızlıkta 101 çıkış kodu verir.
should_panic, Result döndüren testler ve ignore
Hata koşullarını test etmek de önemlidir. #[should_panic] eklenen test, fonksiyon panik yaparsa geçer, yapmazsa başarısız olur. Kitap bunun belirsiz olabileceği konusunda uyarır: test beklediğinizden farklı bir nedenle panik yapsa da geçer. Bu yüzden expected parametresiyle panik mesajında geçmesi gereken bir metin parçası vermeniz önerilir; örnekteki gecersiz_not testi yalnızca mesaj "0 ile 100 arasında" ifadesini içeriyorsa geçer. Ne kadar uzun bir metin vereceğiniz, mesajın ne kadarının sabit olduğuna ve testin ne kadar kesin olmasını istediğinize bağlıdır.
Testler Result<(), E> döndürebilir; böylece gövdede ? operatörünü kullanabilir ve herhangi bir adım Err verdiğinde testi başarısız sayabilirsiniz. Örnekteki metinden_okuma testi bunu gösteriyor. Kitaba göre Result döndüren testlerde #[should_panic] kullanılamaz; bir işlemin hata döndürdüğünü doğrulamak için de ? yerine assert!(deger.is_err()) yazmalısınız. ? operatörü için Rust Result ve hata yönetimi yazısına bakın.
#[ignore] özniteliği testi derler ama çalıştırmaz; Reference'a göre #[ignore = "neden"] biçimiyle bir gerekçe de yazabilirsiniz ve bu gerekçe çıktıda görünür. Yok sayılan testleri yalnız çalıştırmak için cargo test -- --ignored, hepsini birlikte çalıştırmak için cargo test -- --include-ignored kullanılır.
cargo test çıktısını okumak ve bayraklar

Aşağıda birim testlerini tek iş parçacığında çalıştırdık. Paralel çalıştırmada satırların sırası testlerin bitiş sırasına göre değişebilir; --test-threads=1 ile testler adlarına göre sıralı yürüdü. Özet satırındaki alanlar şunlardır: geçen, başarısız, yok sayılan, ölçülen (benchmark) ve filtrelenen test sayısı.
cargo test --lib -- --test-threads=1
# çıktı (birim testi bölümü):
# running 6 tests
# test tests::buyuk_veri ... ignored, uzun sürüyor
# test tests::gecersiz_not - should panic ... ok
# test tests::harf_sinirlari ... ok
# test tests::metinden_okuma ... ok
# test tests::ortalama_dogru ... ok
# test tests::ozel_fonksiyon ... ok
#
# test result: ok. 5 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.00sKitap komut satırı argümanlarının iki gruba ayrıldığını vurgular: -- ayırıcısından önceki argümanlar Cargo'ya, sonrakiler test ikilisine gider. cargo test --help Cargo seçeneklerini, cargo test -- --help test ikilisinin seçeneklerini gösterir. Aynı projede filtreleme ve hedef seçimiyle çalıştırdığımız komutlar:
cargo test harf
# çıktıdan seçilmiş satırlar:
# test tests::harf_sinirlari ... ok
# test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 5 filtered out; finished in 0.00s
cargo test -- --ignored
# çıktıdan seçilmiş satırlar:
# test tests::buyuk_veri ... ok
# test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 5 filtered out; finished in 0.00s
cargo test --test karne
# çıktıdan seçilmiş satır:
# test sinif_karnesi ... ok
cargo test --doc
# çıktıdan seçilmiş satır (Windows'ta yol ayırıcısı ters eğik çizgidir):
# test src\lib.rs - ortalama (line 3) ... ok| Komut | Etkisi |
|---|---|
| cargo test ad | Adında verilen metni içeren testleri çalıştırır; modül adı da test adının parçasıdır |
| cargo test -- --test-threads=1 | Testleri paralel değil tek iş parçacığında çalıştırır |
| cargo test -- --show-output | Geçen testlerin standart çıktısını da gösterir; varsayılan olarak yakalanır |
| cargo test -- --ignored | Yalnızca ignore ile işaretli testleri çalıştırır |
| cargo test --test karne | Yalnızca tests/karne.rs entegrasyon testini çalıştırır |
| cargo test --lib | Yalnızca kütüphanenin birim testlerini çalıştırır |
| cargo test --doc | Yalnızca dokümantasyon testlerini çalıştırır |
| cargo test --no-run | Testleri derler ama çalıştırmaz |
| cargo test --no-fail-fast | Bir test ikilisi başarısız olsa da diğerlerini çalıştırmaya devam eder |
Filtreleme konusunda Kitaptaki bir ayrıntı: cargo test komutuna birden çok test adı veremezsiniz, yalnızca ilk değer kullanılır; birden çok testi seçmek için adlarının ortak bir parçasını verirsiniz. Ayrıca bir test başarılı olduğunda içindeki println! çıktısı gösterilmez, yalnızca başarısız testlerin çıktısı rapora eklenir. --no-fail-fast ise Cargo belgesine göre test ikilisinin tamamına uygulanır; tek bir ikili içindeki testler zaten sonuna kadar çalışır.
Entegrasyon testleri: tests klasörü
Entegrasyon testleri kütüphanenize tamamen dışarıdan bakar ve yalnızca genel (public) API'yi kullanabilir. Amaçları, tek başına doğru çalışan parçaların birlikte de doğru çalıştığını göstermektir. Projenin kökünde, src klasörünün yanında bir tests klasörü oluşturursunuz; Cargo buradaki her dosyayı ayrı bir crate olarak derler. Bu yüzden her dosyanın başında kütüphaneyi use not_hesap::... ile kapsama almanız gerekir ve #[cfg(test)] yazmanız gerekmez, çünkü bu klasör yalnızca cargo test sırasında derlenir.
mod common;
use not_hesap::{harf, ortalama};
#[test]
fn sinif_karnesi() {
let notlar = common::ornek_sinif();
let harfler: String = notlar.iter().map(|&n| harf(n)).collect();
assert_eq!(harfler, "FBAA");
assert_eq!(ortalama(¬lar), Some(74));
}Testler arasında paylaşılan yardımcı kodu tests/common.rs dosyasına koyarsanız Cargo onu da bir test crate'i sayar ve çıktıda içinde hiç test olmayan bir bölüm görürsünüz. Kitabın önerdiği çözüm, eski adlandırma kuralını kullanıp dosyayı tests/common/mod.rs olarak oluşturmaktır; tests altındaki alt klasörlerdeki dosyalar ayrı crate olarak derlenmez. Ardından test dosyasında mod common; yazıp yardımcıyı common::ornek_sinif() diye çağırırsınız.
pub fn ornek_sinif() -> Vec<u32> {
vec![45, 72, 88, 91]
}Yalnızca src/main.rs içeren bir ikili crate'te entegrasyon testi yazamazsınız, çünkü yalnızca kütüphane crate'leri başka crate'lerin kullanabileceği fonksiyonlar sunar. Kitap bu yüzden ikili sağlayan projelerde asıl mantığın src/lib.rs içine konmasını, src/main.rs dosyasının ise bu mantığı çağıran ince bir katman olarak kalmasını önerir. Cargo belgesine göre bir entegrasyon testi seçildiğinde paketin ikili dosyaları da derlenir ve testin onları bulabilmesi için CARGO_BIN_EXE_<ad> ortam değişkeni ayarlanır. Komut satırı programlarının yapısı için Rust komut satırı argümanları yazısına bakın.
Dokümantasyon testleri
/// yorumlarındaki kod blokları da test edilir: cargo test bunları rustdoc ile çıkarıp derler ve çalıştırır. Örnekteki ortalama fonksiyonunun belgesindeki iki assert_eq! satırı, belgenin kodla uyumlu kalmasını garanti ediyor; fonksiyonun davranışı değişip örnek eskirse test başarısız olur. Kod bloğuna eklenen etiketler davranışı değiştirir: ignore bloğu çalıştırmaz, should_panic bloğun panik yapmasını bekler, no_run derler ama çalıştırmaz, compile_fail ise derlemenin başarısız olmasını bekler.
Edition Guide'a göre Rust 2024 edition'da dokümantasyon testleri, önemli bir performans iyileştirmesi için tek bir ikili dosyada birleştirilerek derlenir; bu yalnızca crate'in tamamı 2024 edition olduğunda geçerlidir. Bizim çıktımızın sonunda da birleştirilmiş derlemenin süresini bildiren bir satır göründü. Birleştirilemeyecek bir örnek için standalone_crate etiketi vardır. Dokümantasyon yorumlarının tamamı ve cargo doc için Rust rustdoc dokümantasyon yorumları yazısına bakın.
Paralel çalışma, çalışma dizini ve ortak durum
Testler varsayılan olarak paralel iş parçacıklarında çalıştığı için birbirlerine ya da ortak bir duruma bağımlı olmamalıdır. Kitabın örneği öğreticidir: her test diske aynı adlı bir dosya yazıp okuyorsa, bir test yazmakla okumak arasındayken öbürü dosyanın üzerine yazabilir ve test, kod doğru olduğu halde başarısız olur. Çözüm her teste ayrı bir dosya adı vermek ya da testleri --test-threads=1 ile sırayla çalıştırmaktır. Aynı uyarı ortam değişkenleri ve geçerli çalışma dizini gibi süreç genelindeki durumlar için de geçerlidir.
Cargo belgesine göre birim ve entegrasyon testleri çalışırken çalışma dizini, testin ait olduğu paketin kök klasörüdür; böylece testler cargo test nereden çalıştırılırsa çalıştırılsın paketin dosyalarına göreli yollarla güvenle ulaşabilir. Testleri GitHub Actions gibi bir CI ortamında otomatik çalıştırmak için GitHub Actions CI/CD rehberi yazısına bakabilirsiniz.
Sık yapılan hatalar

- should_panic'i expected olmadan kullanmak: test yanlış nedenle panik yapsa da geçer.
- Testlerin ortak dosya veya ortam değişkeni kullanması: paralel çalışmada rastgele başarısızlıklar üretir.
- Yardımcıları tests/common.rs dosyasına koymak: boş bir test bölümü oluşur; tests/common/mod.rs kullanın.
- Mantığı main.rs içinde tutmak: entegrasyon testleri ikili crate'e erişemez; mantığı lib.rs dosyasına taşıyın.
- Result döndüren testte hatayı ? ile doğrulamaya çalışmak: hata testin başarısız olmasına yol açar; hata beklentisi için assert!(sonuc.is_err()) yazın.
- Yalnızca assert! kullanmak: başarısızlıkta değerler görünmez; eşitlik için assert_eq! tercih edin ve gerekirse açıklama mesajı ekleyin.
Sık Sorulan Sorular
Test sırası neden her seferinde değişiyor?
Testler paralel çalışır ve sonuçlar bittikleri sırayla yazılır. Sabit bir sıra gerekiyorsa cargo test -- --test-threads=1 kullanın; ama testlerinizin sıraya bağımlı olmaması daha doğru bir hedeftir.
Benchmark yazabilir miyim?
Kitaba göre benchmark testleri şu an yalnızca nightly Rust'ta kullanılabilir. Özet satırındaki measured alanı bu testler içindir; cargo test sayfasına göre #[bench] fonksiyonları test sırasında işlevsel olduklarını doğrulamak için bir kez çalıştırılır.
Kendi test ana fonksiyonumu yazabilir miyim?
Evet. Cargo belgesine göre hedefin manifest ayarlarında harness = false yazarak libtest çatısını kapatabilirsiniz; bu durumda testleri çalıştıracak main fonksiyonunu kendiniz sağlarsınız.
Testlerde Clippy uyarılarını da denetleyebilir miyim?
Evet, Clippy test kodunu da denetleyebilir; hangi hedeflerin denetleneceği, lint seviyeleri ve araçların kullanımı Clippy ve rustfmt yazısında anlatılıyor.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: Rust öğrenme rehberi · Cargo.toml ve bağımlılıklar · Rust rustdoc · Rust Result · Rust modül sistemi · Clippy ve rustfmt