Rust is oorspronkelijk ontworpen met de nadruk op betrouwbaarheid en veiligheid, waardoor modulaire testing een onmiskenbaar onderdeel van het ecosysteem van de taal is geworden. Tests zijn geïntegreerd op taal- en toolchain-niveau (cargo test), waardoor ze een organisch onderdeel van het ontwikkelproces vormen.
Achtergrond van de vraag
In traditionele ecosystemen (bijvoorbeeld C/C++, Python, Java) was testing een afzonderlijk aspect van het programma. In Rust zijn tests onderdeel van de code, ze worden gecompileerd en gecontroleerd als een volledig module. Deze synergie wordt bereikt door taalconstructies en compilerkenmerken.
Probleem
Zonder correcte testing is het onmogelijk om de betrouwbaarheid van belangrijke functies te garanderen. Voor complexe en meermodulaire projecten rijst vaak de vraag: hoe organiseer je tests op een handige manier, zonder dat ze afhankelijk zijn van de staat van andere modules en de structuur van het project compliceren?
Oplossing
In Rust worden tests geplaatst binnen het originele bestand (met #[cfg(test)]), of in een aparte map tests voor integratietests. Aan elke module kunnen privé-tests worden toegevoegd, die toegang hebben tot de privé-API.
Voorbeeldcode:
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); } }
Belangrijkste kenmerken:
Wat is de functie van use super::*; in de testmodule?
Om tests toegang te geven tot functies en structuren van de huidige module (inclusief privéfuncties), wordt meestal use super::*; in de test gebruikt.
Kan #[test] asynchroon zijn?
Standaard ondersteunt #[test] geen async in de taal, maar met behulp van externe crates (zoals tokio of async-std) is het mogelijk om async #[test] te maken.
Voorbeeldcode:
#[tokio::test] async fn test_async_add() { assert_eq!(add(2, 2).await, 4); }
Kunnen tests de globale staat veranderen en hoe dit te vermijden?
Tests in Rust worden standaard parallel uitgevoerd, dus het is niet toegestaan om gedeelde globale staat te gebruiken zonder synchronisatie (static mut), anders ontstaan er race condities.
Alle tests gebruiken één globale variabele:
static mut COUNTER: u32 = 0; #[test] fn test_inc() { unsafe { COUNTER += 1; } }
Voordelen:
Nadelen:
Elke test is geïsoleerd, gebruikt lokale variabelen en mock-objecten.
Voordelen:
Nadelen: