Yükleniyor...

Rust Testing: 7 Adımda Kapsamlı & Detaylı Rehber [2026]

Yazar: Burak Balkı | Kategori: Testing | Okuma Süresi: 55 dk

Bu kapsamlı rehber, 2026 yılı itibarıyla Rust projelerinde birim, entegrasyon, performans ve ileri seviye test tekniklerini A'dan Z'ye ele alarak, okuyucular...

# Rust Testing: 7 Adımda Kapsamlı & Detaylı Rehber [2026] ## Giriş: Güvenilir Yazılımın Temeli Olarak Rust Testing Modern yazılım geliştirmede güvenilirlik ve sağlamlık, hiç olmadığı kadar kritik bir rol oynuyor. Özellikle sistem programlama alanında, hata toleransı düşük uygulamalar için test süreçleri hayati önem taşır. Peki, Rust'ın benzersiz güvenlik ve performans garantilerini test süreçlerinize nasıl taşıyabilirsiniz? Bu kapsamlı rehberde, 2026 yılı itibarıyla Rust ekosisteminde **Rust testing** için mevcut olan en iyi pratikleri, araçları ve teknikleri A'dan Z'ye ele alacağız. Bu yazı sayesinde, Rust projelerinizde birim testlerinden ileri seviye fuzz testlerine kadar her aşamada nasıl etkili testler yazacağınızı öğrenecek, böylece daha güvenilir ve bakımı kolay uygulamalar geliştirebileceksiniz. ## Rust Testing Nedir? Rust Testing, Rust programlama dilinde yazılmış uygulamaların ve kütüphanelerin beklenen şekilde çalıştığını doğrulamak için kullanılan otomatik test süreçlerinin genel adıdır. Bu süreç, kodun doğru davranıp davranmadığını, hataları yakalayıp yakalamadığını ve performans kriterlerini karşılayıp karşılamadığını kontrol eder. Rust'ın yerleşik test altyapısı ve Cargo test runner'ı sayesinde geliştiriciler, birim, entegrasyon ve dokümantasyon testlerini kolayca yazabilir ve çalıştırabilirler. Bu, özellikle bellek güvenliği ve eşzamanlılık gibi kritik konularda Rust'ın sunduğu garantileri kod seviyesinde de teyit etmeyi sağlar. Rust'ın test felsefesi, dilin kendisi gibi güvenilirliğe odaklanır. Derleyici, birçok hatayı derleme zamanında yakalayarak test yükünü azaltırken, kalan mantıksal hataların tespiti için kapsamlı bir test süiti olmazsa olmazdır. 2026 itibarıyla Rust'ın kararlı sürümü olan 1.80.0 ve Cargo 1.80.0 ile birlikte gelen güçlü test araçları, geliştiricilere sağlam ve sürdürülebilir kod tabanları oluşturma imkanı sunar. ## Neden Rust Testing Kullanmalısınız? Rust, performans ve güvenlik odaklı bir dil olmasıyla öne çıkar. Bu özelliklerin kodunuzda da devam ettiğinden emin olmak için kapsamlı testler şarttır. İşte Rust testing kullanmanın başlıca nedenleri: * **Hata Yakalama:** Rust'ın derleyici garantileri birçok sınıf hatayı önlese de, mantıksal hatalar veya karmaşık iş akışlarındaki yanlışlıklar ancak testlerle ortaya çıkarılabilir. Testler, regression hatalarını erken aşamada yakalayarak üretim ortamına sızmasını engeller. * **Kod Kalitesi ve Güvenilirliği:** İyi yazılmış testler, kodunuzun kalitesini artırır. Geliştiricilerin, kodlarında değişiklik yaparken mevcut işlevselliği bozmadıklarından emin olmalarını sağlar. Bu, özellikle büyük ve karmaşık projelerde kritik bir güvence mekanizmasıdır. * **Refactoring Güvenliği:** Mevcut kodu yeniden düzenlerken (refactoring), testler bir güvenlik ağı görevi görür. Testleriniz varsa, kodunuzu daha cesurca optimize edebilir veya yapısını değiştirebilirsiniz, çünkü herhangi bir bozulma anında tespit edilecektir. * **Dokümantasyon ve Anlaşılabilirlik:** Testler, kodun nasıl kullanılması gerektiğini ve hangi senaryolarda nasıl davranması gerektiğini gösteren canlı bir dokümantasyon görevi görebilir. Özellikle `doc tests` bu konuda çok etkilidir. * **Performans Garantisi:** `criterion` gibi kütüphanelerle performans testleri yazarak, kodunuzun belirli performans eşiklerini karşıladığından emin olabilirsiniz. Bu, özellikle düşük gecikme süresi gerektiren sistemler için önemlidir. * **Geliştirici Verimliliği:** Otomatik testler, manuel test süreçlerine kıyasla zaman ve kaynak tasarrufu sağlar. Geliştiriciler, kodlarını hızlı bir şekilde test edip geri bildirim alarak daha verimli çalışabilirler. Rust'ın sunduğu güvenlik ve performans avantajlarını tam anlamıyla kullanabilmek için, bu avantajları test stratejinize de entegre etmeniz gerekmektedir. Ekibimizde Rust'a geçiş sürecinde öğrendiğimiz 3 kritik ders şunlar oldu: 1) Test kapsamını asla ihmal etmeyin, 2) Entegrasyon testlerine özel önem verin, 3) Performans kritik bileşenler için benchmark testleri yazın. Bu yaklaşımlar, projelerimizin sağlamlığını ve sürdürülebilirliğini önemli ölçüde artırdı. ## Rust Testing vs. Alternatifler Rust, yerleşik test altyapısı sayesinde güçlü bir başlangıç sunar. Ancak, ekosistemde farklı ihtiyaçlara yönelik alternatifler ve tamamlayıcı araçlar da bulunmaktadır. İşte Rust'ın kendi test mekanizması ile bazı popüler test yaklaşımlarının karşılaştırması: | Özellik | Rust'ın Yerleşik `cargo test` | `criterion` (Performans) | `mockall` (Mocking) | Python `pytest` (Karşılaştırma) | Go `testing` paketi (Karşılaştırma) | | :------------------ | :----------------------------- | :----------------------- | :-------------------------- | :------------------------------ | :---------------------------------- | | **Tipi** | Birim, Entegrasyon, Dokümantasyon | Performans (Benchmark) | Mocking/Stubbing | Birim, Entegrasyon | Birim, Entegrasyon | | **Öğrenme Eğrisi** | Düşük (Dil ile entegre) | Orta | Orta | Düşük-Orta | Düşük | | **Ekosistem** | Çekirdek dilin parçası | Aktif, popüler crate | Aktif, popüler crate | Geniş, zengin eklenti | Çekirdek dilin parçası | | **Topluluk** | Çok geniş | Büyük | Büyük | Çok büyük | Çok büyük | | **Kurumsal Destek** | Yüksek (Resmi) | Orta-Yüksek | Orta-Yüksek | Yüksek | Yüksek | | **Kullanım Alanı** | Genel testler | Kritik performans ölçümü | Bağımlılıkları izole etme | Genel testler | Genel testler | Rust'ın kendi test altyapısı, çoğu birim ve entegrasyon testi ihtiyacını karşılamak için oldukça yeterlidir. Ancak performans analizi için `criterion` gibi özel kütüphaneler, bağımlılıkları izole etmek için `mockall` gibi mocking araçları devreye girer. Diğer dillerdeki test çerçeveleriyle karşılaştırıldığında, Rust'ın derleme zamanı garantileri sayesinde birçok hatanın derleme aşamasında yakalanması, testlerin odak noktasını daha çok mantıksal hatalara kaydırır. Bu da Rust testlerini diğer dillere göre daha az boilerplate kodu gerektiren ve daha odaklı hale getiren bir avantajdır. ## Kurulum ve İlk Adımlar: Rust Test Ortamını Hazırlama Rust projelerinde test yazmak ve çalıştırmak, Cargo'nun entegre test runner'ı sayesinde oldukça basittir. 2026 yılı itibarıyla Rust 1.80.0 ile birlikte gelen Cargo, testleri yönetmek için güçlü bir araçtır. İşte Rust test ortamını kurmak ve ilk testinizi çalıştırmak için adım adım yapmanız gerekenler: ### Ön Gereksinimler * **Rustup Kurulumu:** Rust programlama dilinin ve Cargo paket yöneticisinin sisteminizde yüklü olması gerekir. Eğer yüklü değilse, rustup.rs adresinden kolayca kurabilirsiniz. ```bash curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh source $HOME/.cargo/env # Veya terminalinize göre path'i ayarlayın rustup update # Rust ve Cargo'yu en son kararlı sürüme güncelleyin (örn. 1.80.0) ``` * **Bir Rust Projesi:** Testleri yazmak için mevcut bir Rust projeniz olmalı veya yeni bir proje oluşturmalısınız. ```bash cargo new my_rust_app --lib # Yeni bir kütüphane projesi oluştur cd my_rust_app ``` ### Adım 1: İlk Birim Testini Yazma Rust'ta birim testleri, genellikle test edilen modülün bulunduğu aynı dosyada, `#[cfg(test)]` niteliğiyle işaretlenmiş bir `mod test` bloğunun içine yazılır. Bu, test kodunun sadece `cargo test` komutu çalıştırıldığında derlenmesini sağlar. `src/lib.rs` dosyanızı açın ve aşağıdaki kodu ekleyin: ```rust // src/lib.rs pub fn add_two(a: i32) -> i32 { a + 2 } #[cfg(test)] // Bu modül sadece test sırasında derlenecek mod tests { // Dış modüldeki fonksiyonlara erişmek için 'super' kullanılır use super::*; #[test] // Bu fonksiyonun bir test olduğunu belirtir fn it_adds_two() { // assert_eq! makrosu, iki değerin eşit olup olmadığını kontrol eder assert_eq!(add_two(2), 4); } #[test] fn another_test() { assert_ne!(add_two(2), 5); // Eşit olmama kontrolü } #[test] #[should_panic(expected = "panic message")] // Beklenen bir panic durumunu test eder fn test_panics() { // Bu fonksiyonun panic atmasını bekliyoruz // Gerçek bir senaryoda bu, hata koşullarını test etmek için kullanılır if add_two(0) == 2 { panic!("panic message"); } } } ``` ### Adım 2: Testleri Çalıştırma Testleri çalıştırmak için projenizin kök dizininde (yani `Cargo.toml` dosyasının bulunduğu yerde) basitçe `cargo test` komutunu kullanın. ```bash # Projenin kök dizininde cargo test ``` **Beklenen Çıktı:** ```text Compiling my_rust_app v0.1.0 (~/my_rust_app) Finished test [unoptimized + debuginfo] target(s) in 0.52s Running unittests (target/debug/deps/my_rust_app-a1b2c3d4e5f67890) running 3 tests test tests::it_adds_two ... ok test tests::another_test ... ok test tests::test_panics ... ok test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Doc-tests my_rust_app running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ``` Bu çıktı, tüm testlerinizin başarılı bir şekilde geçtiğini gösterir. `cargo test` varsayılan olarak hem birim testlerini hem de dokümantasyon testlerini çalıştırır. Belirli bir testi çalıştırmak için test fonksiyonunun adını argüman olarak verebilirsiniz: ```bash cargo test it_adds_two ``` ### Adım 3: Entegrasyon Testleri Yazma Entegrasyon testleri, kütüphanenizin dışarıdan nasıl davrandığını veya birden fazla modülün bir araya geldiğinde nasıl çalıştığını test etmek için kullanılır. Rust'ta entegrasyon testleri, projenizin kök dizinindeki `tests` adında özel bir dizinde yer alır. Projenizin kök dizininde `tests` adında bir klasör oluşturun: ```bash mkdir tests ``` Ardından, `tests/integration_test.rs` adında bir dosya oluşturun ve içine aşağıdaki kodu ekleyin: ```rust // tests/integration_test.rs // Kütüphanemizi dışarıdan bir crate gibi içe aktarıyoruz use my_rust_app::add_two; #[test] fn it_works() { assert_eq!(add_two(2), 4); } #[test] fn another_integration_test() { let result = 2 + 2; assert_eq!(result, 4); } ``` Bu entegrasyon testlerini çalıştırmak için yine `cargo test` komutunu kullanmanız yeterlidir. Cargo, `tests` dizinindeki tüm `*.rs` dosyalarını otomatik olarak bulur ve derler. ```bash cargo test ``` **Beklenen Çıktı:** (Önceki birim testleri de dahil) ```text Compiling my_rust_app v0.1.0 (~/my_rust_app) Finished test [unoptimized + debuginfo] target(s) in 0.52s Running unittests (target/debug/deps/my_rust_app-a1b2c3d4e5f67890) running 3 tests test tests::it_adds_two ... ok test tests::another_test ... ok test tests::test_panics ... ok test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Running integration tests (target/debug/deps/integration_test-a1b2c3d4e5f67890) running 2 tests test it_works ... ok test another_integration_test ... ok test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Doc-tests my_rust_app running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ``` Bu adımlar, Rust projelerinizde temel test altyapısını kurmanız için yeterlidir. Bir sonraki bölümde, farklı test türlerini ve daha pratik örnekleri inceleyeceğiz. ## Temel Kullanım ve Örnekler: Çeşitli Test Türleri Rust, birim testleri, entegrasyon testleri ve dokümantasyon testleri gibi farklı test türlerini destekler. Her birinin kendine özgü kullanım alanları ve faydaları vardır. İşte bu test türlerine dair pratik örnekler: ### 1. Birim Testleri (Unit Tests) Birim testleri, kodun en küçük, izole edilebilir parçalarını (fonksiyonlar, metotlar) test etmeye odaklanır. Genellikle test edilen modülün aynı dosyasında `#[cfg(test)]` bloğu içinde yer alırlar. **Problem:** Bir matematik kütüphanesindeki `factorial` fonksiyonunun doğru çalıştığını doğrulamak. **Çözüm:** Farklı girdiler için `factorial` fonksiyonunu test eden birim testleri yazmak. ```rust // src/math_utils.rs pub fn factorial(n: u64) -> u64 { if n == 0 { 1 } else { n * factorial(n - 1) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_factorial_zero() { assert_eq!(factorial(0), 1); } #[test] fn test_factorial_one() { assert_eq!(factorial(1), 1); } #[test] fn test_factorial_five() { assert_eq!(factorial(5), 120); } #[test] #[should_panic(expected = "attempt to multiply with overflow")] fn test_factorial_overflow() { // u64 için factorial(21) overflow yapar // Bu testin panic atmasını bekliyoruz factorial(21); } } ``` **Çalıştırma:** `cargo test --bin math_utils` (eğer `math_utils` bir bin crate ise) veya `cargo test` (eğer bir lib crate ise). ### 2. Entegrasyon Testleri (Integration Tests) Entegrasyon testleri, kütüphanenizin dışarıdan nasıl davrandığını test eder. `tests` dizininde ayrı dosyalarda bulunurlar ve kütüphanenizi dış bir `crate` gibi kullanırlar. **Problem:** Bir web API'si için tanımlanmış rota işleyicilerinin (handler) doğru HTTP yanıtları döndürdüğünü doğrulamak. **Çözüm:** `reqwest` gibi bir HTTP istemcisi kullanarak API uç noktalarına istek gönderip yanıtları kontrol etmek. ```rust // Cargo.toml'a ekleyin: // [dev-dependencies] // reqwest = { version = "0.11", features = ["json"] } // tokio = { version = "1", features = ["full"] } // assert_json_diff = "2.0" // tests/api_integration_test.rs use reqwest::StatusCode; use serde_json::json; // Varsayımsal bir web sunucusunun 8080 portunda çalıştığını varsayalım // Gerçek bir senaryoda, testten önce sunucuyu başlatmak gerekebilir. #[tokio::test] async fn test_get_users_endpoint() { let client = reqwest::Client::new(); let res = client.get("http://127.0.0.1:8080/users") .send() .await .expect("API isteği başarısız oldu"); assert_eq!(res.status(), StatusCode::OK); let body: serde_json::Value = res.json().await.expect("JSON parse edilemedi"); // Örnek bir JSON yapısını kontrol ediyoruz // assert_json_diff crate'i ile daha detaylı karşılaştırmalar yapılabilir assert!(body.as_array().is_some()); assert!(!body.as_array().unwrap().is_empty()); } #[tokio::test] async fn test_post_new_user() { let client = reqwest::Client::new(); let new_user = json!({ "username": "testuser_2026", "email": "test@example.com" }); let res = client.post("http://127.0.0.1:8080/users") .json(&new_user) .send() .await .expect("API isteği başarısız oldu"); assert_eq!(res.status(), StatusCode::CREATED); let body: serde_json::Value = res.json().await.expect("JSON parse edilemedi"); assert_eq!(body["username"], "testuser_2026"); } ``` **Çalıştırma:** `cargo test` > **Pro Tip:** Entegrasyon testleri için genellikle gerçek bir veritabanı veya harici servislerle etkileşime girilmesi gerekebilir. Bu durumda, testler için ayrı bir test veritabanı kullanmak veya servisleri `mock`lamak iyi bir pratik olacaktır. `testcontainers` gibi kütüphaneler, testleriniz için geçici Docker container'ları başlatmanıza olanak tanır. ### 3. Dokümantasyon Testleri (Documentation Tests) Rust'ın en eşsiz özelliklerinden biri, dokümantasyonunuzdaki kod örneklerini otomatik olarak test edebilmesidir. Bu, dokümantasyonunuzun her zaman güncel ve doğru kalmasını sağlar. **Problem:** Kütüphanenizin kullanımını gösteren örneklerin zamanla bozulmasını engellemek. **Çözüm:** Dokümantasyon yorumları (`///`) içine kod örnekleri eklemek ve bunları `cargo test` ile çalıştırmak. ```rust // src/lib.rs /// # My Awesome Library /// /// Bu kütüphane, basit matematiksel işlemler sunar. /// /// # Örnekler /// /// ``` /// use my_lib::add; /// assert_eq!(add(2, 3), 5); /// ``` /// /// ```should_panic /// use my_lib::divide; /// // Sıfıra bölme hatası bekliyoruz /// divide(10, 0); /// ``` pub fn add(a: i32, b: i32) -> i32 { a + b } pub fn divide(a: i32, b: i32) -> i32 { if b == 0 { panic!("Cannot divide by zero!"); } a / b } ``` **Çalıştırma:** `cargo test` (dokümantasyon testleri varsayılan olarak çalışır) ### 4. Gözardı Edilen Testler (Ignored Tests) Bazen belirli testleri, örneğin uzun süren veya sadece belirli koşullarda çalışması gereken testleri göz ardı etmek isteyebilirsiniz. `#[ignore]` niteliği bu iş için kullanılır. **Problem:** Uzun süren bir performans testinin her `cargo test` çalıştırıldığında çalışmasını engellemek. **Çözüm:** Test fonksiyonunu `#[ignore]` ile işaretlemek. ```rust // src/lib.rs (tests modülü içinde) #[test] #[ignore] fn expensive_test() { // Bu test çok uzun sürer, bu yüzden normalde çalıştırılmaz. // Sadece 'cargo test -- --ignored' ile çalıştırılabilir. std::thread::sleep(std::time::Duration::from_secs(5)); assert!(true); } ``` **Çalıştırma:** * Normalde göz ardı edilir: `cargo test` * Sadece göz ardı edilenleri çalıştır: `cargo test -- --ignored` * Tüm testleri çalıştır (göz ardı edilenler dahil): `cargo test -- --include-ignored` Bu temel örnekler, Rust'ın yerleşik test özellikleriyle neler yapabileceğinizi göstermektedir. Bir sonraki bölümde, daha ileri seviye test tekniklerini inceleyeceğiz. ## İleri Seviye Teknikler: Güçlü Test Stratejileri Rust'ın temel test mekanizmalarının ötesine geçerek, projelerinizin daha da sağlam olmasını sağlayacak ileri seviye teknikleri keşfedelim. Bu teknikler, özellikle üretim ortamında karşılaşabileceğiniz edge case'leri ve performans darboğazlarını tespit etmenize yardımcı olacaktır. ### 1. Performans Testleri (Benchmarking) ile `criterion` Performans kritik uygulamalarda, kod değişikliklerinin performansı olumsuz etkilemediğinden emin olmak önemlidir. Rust'ın yerleşik benchmark testleri `nightly` derleyici gerektirirken, `criterion` crate'i 2026 itibarıyla kararlı Rust üzerinde performans testleri yazmak için endüstri standardı haline gelmiştir. **Problem:** Bir fonksiyonun farklı girdilerle ne kadar hızlı çalıştığını objektif olarak ölçmek ve regresyonları tespit etmek. **Çözüm:** `criterion` kütüphanesini kullanarak benchmark testleri yazmak. **Kurulum:** `Cargo.toml` dosyanıza `criterion`'u ekleyin: ```toml # Cargo.toml [dev-dependencies] criterion = { version = "0.5", features = ["html_reports"] } [[bench]] name = "my_benchmark" harness = false ``` `benches` dizini oluşturun ve `benches/my_benchmark.rs` dosyasını ekleyin: ```rust // benches/my_benchmark.rs use criterion::{criterion_group, criterion_main, Criterion}; use my_rust_app::factorial; // Önceki bölümdeki factorial fonksiyonumuzu kullanıyoruz fn criterion_benchmark(c: &mut Criterion) { c.bench_function("factorial 10", |b| b.iter(|| factorial(10))); c.bench_function("factorial 20", |b| b.iter(|| factorial(20))); let mut group = c.benchmark_group("expensive_calculations"); group.warm_up_time(std::time::Duration::from_secs(1)); // Isınma süresi group.measurement_time(std::time::Duration::from_secs(5)); // Ölçüm süresi group.bench_function("fibonacci 30", |b| { b.iter(|| { // Varsayımsal bir fibonacci fonksiyonu fn fibonacci(n: u64) -> u64 { match n { 0 => 1, 1 => 1, _ => fibonacci(n - 1) + fibonacci(n - 2), } } fibonacci(30) }) }); group.finish(); } criterion_group!(benches, criterion_benchmark); criterion_main!(benches); ``` **Çalıştırma:** `cargo bench` `criterion`, test sonuçlarını grafiklerle birlikte HTML raporları olarak `target/criterion` dizinine kaydeder. Bu raporlar, performans değişikliklerini görsel olarak takip etmenizi sağlar. ### 2. Fuzz Testing ile `cargo-fuzz` Fuzz testing (fuzzing), rastgele veya yarı rastgele verilerle programları besleyerek beklenmedik davranışları, çökmeleri veya güvenlik açıklarını bulmayı amaçlayan bir tekniktir. Özellikle ağ protokolleri, dosya ayrıştırıcıları veya karmaşık veri yapıları üzerinde çalışan kodlar için çok değerlidir. **Problem:** Kodunuzun beklenmedik veya kötü niyetli girdilere karşı ne kadar dayanıklı olduğunu test etmek. **Çözüm:** `cargo-fuzz` aracı ile fuzz testleri yazmak. **Kurulum:** ```bash cargo install cargo-fuzz cargo fuzz init # Projenizin kök dizininde fuzzing altyapısını kurar ``` Bu komut, `fuzz` adında yeni bir dizin oluşturacak ve örnek bir fuzz hedefi (`fuzz/fuzz_targets/fuzz_target_1.rs`) ekleyecektir. **Örnek Fuzz Testi:** Bir JSON ayrıştırıcısını fuzz etmek. ```rust // fuzz/fuzz_targets/json_parser_fuzz.rs #![no_main] use libfuzzer_sys::fuzz_target; use serde_json::Value; // Cargo.toml'a serde_json eklemeyi unutmayın fuzz_target!(|data: &[u8]| { // Rastgele bayt dizisini String'e dönüştürmeye çalışın if let Ok(s) = std::str::from_utf8(data) { // JSON olarak ayrıştırmaya çalışın let _ = serde_json::from_str::(s); } }); ``` **Çalıştırma:** ```bash cargo fuzz run json_parser_fuzz ``` Fuzzing, genellikle uzun süreler boyunca çalıştırılır ve bulduğu hataları `fuzz/artifacts` dizininde kaydeder. ### 3. Property-Based Testing (Özellik Tabanlı Test) ile `proptest` Birim testleri belirli örnek girdilerle çalışırken, özellik tabanlı testler (PBT) bir fonksiyonun veya sistemin belirli özelliklerini (properties) doğrular. Bu özellikler, tüm geçerli girdiler kümesi için doğru kalmalıdır. **Problem:** Bir fonksiyonun geniş bir girdi aralığı için mantıksal olarak doğru davrandığını kanıtlamak. **Çözüm:** `proptest` crate'ini kullanarak özellik tabanlı testler yazmak. **Kurulum:** `Cargo.toml` dosyanıza `proptest`'i ekleyin: ```toml # Cargo.toml [dev-dependencies] proptest = "1.0" ``` **Örnek Proptest:** Bir `reverse_string` fonksiyonunun ters çevrilip tekrar ters çevrildiğinde orijinal string'e eşit olması özelliğini test etmek. ```rust // src/lib.rs pub fn reverse_string(s: &str) -> String { s.chars().rev().collect() } #[cfg(test)] mod tests { use super::*; use proptest::prelude::*; proptest! { #[test] fn test_reverse_twice_is_original(s in ".*") { // Bir string'i iki kez ters çevirmek orijinal string'i vermelidir. prop_assert_eq!(reverse_string(&reverse_string(&s)), s); } #[test] fn test_reversed_length_is_same(s in ".*") { // Ters çevrilmiş string'in uzunluğu orijinaliyle aynı olmalıdır. prop_assert_eq!(reverse_string(&s).len(), s.len()); } #[test] fn test_reverse_empty_string_is_empty(s in "") { // Boş string ters çevrildiğinde boş olmalıdır. prop_assert_eq!(reverse_string(&s), ""); } } } ``` **Çalıştırma:** `cargo test` `proptest`, belirlenen stratejilere göre rastgele girdiler üretir ve özelliklerin binlerce kez test edilmesini sağlar. Bir hata bulduğunda, hatayı tekrarlamak için en küçük girdi setini rapor eder. ### 4. Mocking ve Stubbing ile `mockall` Karmaşık sistemlerde, test ettiğiniz bir bileşenin harici bağımlılıkları (veritabanları, ağ servisleri, diğer modüller) olabilir. Bu bağımlılıkları izole etmek ve testleri daha hızlı, güvenilir ve tekrarlanabilir hale getirmek için mocking ve stubbing kullanılır. `mockall` crate'i, Rust'ta bu işlevselliği sağlar. **Problem:** Harici bir servis bağımlılığı olan bir fonksiyonu test etmek, ancak gerçek servise bağlanmak istememek. **Çözüm:** `mockall` kullanarak bağımlılığı taklit etmek. **Kurulum:** `Cargo.toml` dosyanıza `mockall`'u ekleyin: ```toml # Cargo.toml [dev-dependencies] mockall = "0.12" ``` **Örnek Mocking:** Bir `UserRepository` trait'ini mock'lamak. ```rust // src/lib.rs // Gerçek kullanıcı struct'ı pub struct User { pub id: u32, pub name: String } // Kullanıcı veritabanı işlemleri için bir trait pub trait UserRepository { fn get_user_by_id(&self, id: u32) -> Option; fn save_user(&self, user: User) -> Result<(), String>; } // Bu trait'i kullanan bir servis pub struct UserService { repository: R, } impl UserService { pub fn new(repository: R) -> Self { UserService { repository } } pub fn get_user_info(&self, id: u32) -> Option { self.repository.get_user_by_id(id) .map(|user| format!("User ID: {}, Name: {}", user.id, user.name)) } } #[cfg(test)] mod tests { use super::*; use mockall::*; // UserRepository trait'inin mock'unu oluştur mock! { MyUserRepository {} impl UserRepository for MyUserRepository { fn get_user_by_id(&self, id: u32) -> Option; fn save_user(&self, user: User) -> Result<(), String>; } } #[test] fn test_get_user_info_exists() { let mut mock_repo = MockMyUserRepository::new(); mock_repo.expect_get_user_by_id() .with(eq(1)) .times(1) // Bu metodun bir kez çağrılmasını bekliyoruz .returning(|_| Some(User { id: 1, name: "Alice".to_string() })); let user_service = UserService::new(mock_repo); let info = user_service.get_user_info(1); assert_eq!(info, Some("User ID: 1, Name: Alice".to_string())); } #[test] fn test_get_user_info_not_found() { let mut mock_repo = MockMyUserRepository::new(); mock_repo.expect_get_user_by_id() .with(eq(2)) .times(1) .returning(|_| None); let user_service = UserService::new(mock_repo); let info = user_service.get_user_info(2); assert_eq!(info, None); } } ``` **Çalıştırma:** `cargo test` `mockall`, belirli metot çağrılarının sayısını, argümanlarını ve dönüş değerlerini belirlemenize olanak tanır. Bu, bağımlılıkları olan karmaşık mantıkları test ederken esneklik sağlar. Bu ileri seviye teknikler, Rust projelerinizde daha kapsamlı, dayanıklı ve performans odaklı testler yazmanıza olanak tanır. Özellikle büyük ölçekli ve kritik sistemlerde bu yaklaşımlar, yazılım kalitesini önemli ölçüde artırır. ## Best Practices & Anti-Patterns: Etkili Rust Testleri İçin İpuçları [2026] Etkili testler yazmak sadece doğru araçları kullanmakla kalmaz, aynı zamanda belirli prensipleri ve yaklaşımları takip etmeyi de gerektirir. İşte 2026 yılı itibarıyla Rust testing için kabul görmüş en iyi pratikler ve kaçınılması gereken anti-pattern'lar: ### ✅ Best Practices * **Testleri Modüllerin Yanına Yerleştirin (`#[cfg(test)]`):** Birim testleri için, test edilen kodla aynı dosyada `#[cfg(test)] mod tests { ... }` bloğunu kullanın. Bu, kod ve test arasındaki bağlamı korur ve geliştiricilerin ilgili testleri kolayca bulmasını sağlar. **Neden:** Testlerin kodla birlikte evrimleşmesini kolaylaştırır ve bağlamı korur. * **Entegrasyon Testlerini `tests` Dizinine Koyun:** Kütüphanenizin dış arayüzünü test eden entegrasyon testlerini projenizin kök dizinindeki `tests` klasörüne yerleştirin. Her bir test dosyası ayrı bir entegrasyon `crate`'i olarak derlenir. **Neden:** Kütüphanenin dışarıdan nasıl kullanıldığını simüle eder ve modüller arası etkileşimleri test eder. * **Dokümantasyon Testlerini Kullanın:** `///` ile yazılan dokümantasyon örneklerinizin `cargo test` ile çalıştığından emin olun. Bu, dokümantasyonunuzun daima güncel kalmasını sağlar. **Neden:** Dokümantasyonun doğruluğunu garanti eder ve kodun kullanımına dair canlı örnekler sunar. * **Test Fonksiyonlarını Açıklayıcı Adlandırın:** Test fonksiyonlarınızın neyi test ettiğini açıkça belirten isimler kullanın (örn: `test_add_two_positive_numbers`, `test_divide_by_zero_panics`). **Neden:** Testlerin amacını hızlıca anlamayı sağlar ve hata ayıklamayı kolaylaştırır. * **Küçük ve Odaklı Testler Yazın:** Her testin tek bir şeyi test ettiğinden ve mümkün olduğunca izole olduğundan emin olun. **Neden:** Testlerin başarısız olması durumunda sorunu tespit etmeyi kolaylaştırır. * **`assert!` ve `assert_eq!` Makrolarını Etkin Kullanın:** Beklenen ve gerçek değerleri karşılaştırmak için bu makroları kullanın. Hata mesajlarını açıklayıcı hale getirin. **Neden:** Test sonuçlarının net ve anlaşılır olmasını sağlar. * **`#[should_panic]` Niteliğini Kullanın:** Bir fonksiyonun belirli koşullar altında panic atmasını bekliyorsanız bu niteliği kullanın ve isteğe bağlı olarak beklenen panic mesajını belirtin. **Neden:** Hata senaryolarının doğru işlendiğini doğrular. * **Test Verilerini Yönetin:** Karmaşık testler için test verilerini (fixture'lar) ayrı modüllerde veya dosyalarda düzenli bir şekilde saklayın. **Neden:** Testlerin okunabilirliğini ve bakımını kolaylaştırır. * **CI/CD Süreçlerine Entegre Edin:** Testleri sürekli entegrasyon/sürekli dağıtım (CI/CD) pipeline'ınızın bir parçası haline getirin. Her kod değişikliğinde otomatik olarak çalıştırılmalarını sağlayın. **Neden:** Hataların erken tespiti ve hızlı geri bildirim döngüsü sağlar. * **Performans Kritik Kodlar İçin Benchmark Testleri Yazın:** `criterion` gibi araçlarla performans testleri yazarak, kodunuzun performansının zamanla kötüleşmediğinden emin olun. **Neden:** Performans regresyonlarını proaktif olarak tespit eder. ### ❌ Anti-Patterns (Kaçınılması Gerekenler) * **Çok Büyük Test Fonksiyonları:** Tek bir test fonksiyonunda birden fazla bağımsız özelliği test etmek. **Neden:** Testin başarısız olması durumunda hatanın kaynağını bulmayı zorlaştırır. * **Testlerde Yan Etkiler Bırakmak:** Testlerin, çalıştıkları orta