Rust, güvenilirlik ve güvenlik üzerine odaklanarak tasarlanmıştır, bu nedenle modüler testler dilin ekosisteminin ayrılmaz bir parçası haline gelmiştir. Testler dil ve araç zinciri (cargo test) düzeyinde entegre edilmiştir, bu da onları geliştirme sürecinin organik bir parçası haline getirir.
Konunun Tarihçesi
Geleneksel ekosistemlerde (örneğin C/C++, Python, Java) testler genellikle programdan ayrı olarak var olmuştur. Rust'ta testler, kodun bir parçasıdır ve tam bir modül olarak derlenir ve kontrol edilir. Bu tür bir sinerji, dil yapıları ve derleyici özellikleri sayesinde elde edilmektedir.
Sorun
Doğru testler olmadan, temel işlevlerin güvenilirliğini garanti etmek mümkün değildir. Karmaşık ve çok modüllü projelerde sık sık şu soru ortaya çıkar: Testleri nasıl rahat bir şekilde düzenleyebilir, onları diğer modüllerin durumuna bağlı hale getirmeden nasıl organize edebiliriz ve projenin yapısını karmaşık hale getirmeden nasıl yönetebiliriz?
Çözüm
Rust'ta testler, kaynak dosyasının içinde (#[cfg(test)] kullanarak) ya da entegrasyon testleri için ayrı bir tests klasöründe yer alır. Her modüle, özel bir API'ye erişim sağlayan özel testler eklenebilir.
Kod örneği:
pub fn add(a: i32, b: i32) -> i32 { a + b } #[cfg(test)] mod tests { use super::*; #[test] fn test_add_positive() { assert_eq!(add(2, 3), 5); } #[test] fn test_add_negative() { assert_eq!(add(-1, -3), -4); } }
Anahtar özellikler:
Müze super::*; test modülünde neden gereklidir?
Testlerin mevcut modülün işlevlerine ve yapılara (özel olanlar dahil) erişebilmesi için, testte genellikle use super::*; kullanılır.
#[test] asenkron olabilir mi?
Dil varsayılan olarak #[test] için async desteği sunmaz, ancak dış crate'ler (örneğin tokio veya async-std) kullanarak async #[test] oluşturmak mümkündür.
Kod örneği:
#[tokio::test] async fn test_async_add() { assert_eq!(add(2, 2).await, 4); }
Testler global durumu değiştirebilir mi ve bunu nasıl önleyebiliriz?
Rust'taki testler varsayılan olarak paralel olarak çalıştığı için, senkronize olmadan paylaşılan global durumu (static mut) kullanmak mümkün değildir; aksi takdirde yarış koşulları meydana gelir.
Tüm testler aynı global değişkeni kullanır:
static mut COUNTER: u32 = 0; #[test] fn test_inc() { unsafe { COUNTER += 1; } }
Artılar:
Eksiler:
Her test izole edilmiş, yerel değişkenler ve mock nesneleri kullanır.
Artılar:
Eksiler: