Code testen is een van de oudste en belangrijkste processen in de softwareontwikkeling. In veel talen worden testbibliotheken echter apart geleverd, en de integratie van tests in de hoofdcode kan ondoorzichtig of ongemakkelijk zijn. Rust is vanaf de eerste versies ontworpen met ondersteuning voor expliciete modulaire testing "out-of-the-box".
In veel traditionele talen zijn tests gescheiden of vereisen ze de instellingen van specifieke frameworks en build-tools. Dit bemoeilijkt samenwerkingsontwikkeling, vergroot het risico op desynchronisatie van de hoofdlogica en tests, en bemoeilijkt de uitvoering van integratie- en modulair testen.
Rust integreert het testsysteem direct op de taalniveau: de module #[cfg(test)], de directive #[test] en de tool cargo test stellen ontwikkelaars in staat om tests te schrijven, uit te voeren en te controleren binnen de broncode. Dit zorgt voor een nauwe band tussen de code en de bijbehorende tests, garandeert eenvoud in het uitvoeren van tests en automatiseert CI/CD-processen.
Code voorbeeld:
// tests automatisch gecompileerd en uitgevoerd door `cargo test` #[cfg(test)] mod tests { use super::*; #[test] fn it_adds_two() { assert_eq!(2 + 2, 4); } }
Belangrijke kenmerken:
cargo test) voert alle gevonden tests uit.#[cfg(test)] zorgt ervoor dat tests niet in de release build worden opgenomen.Kun je onstabiele of privé-functies in tests gebruiken, en hoe doe je dat?
Ja, dat kan. Binnen de testmodule, gedefinieerd als #[cfg(test)], kunnen tests niet alleen de publieke, maar ook de private API van de huidige module zien. Dit betekent dat je implementatiedetails direct kunt testen. Echter, van een andere module alleen via de publieke interface.
Voorbeeld:
fn private_internal(x: i32) -> i32 { x + 1 } #[cfg(test)] mod tests { use super::*; #[test] fn test_private() { assert_eq!(private_internal(41), 42); } }
Zullen tests met dezelfde namen conflicteren tussen verschillende modules?
Nee, testnamen zijn alleen zichtbaar binnen hun module. Tests van verschillende modules kunnen dezelfde naam hebben, omdat de compiler ze onderscheidt op basis van het volledige pad (namespace).
Voorbeeld:
mod a { #[cfg(test)] mod tests { #[test] fn basic() {} } } mod b { #[cfg(test)] mod tests { #[test] fn basic() {} } }
Kun je een specifieke test of een deel van een set tests uitvoeren?
Ja, door de naam van de test te filteren: cargo test test_naam. Dit is handig voor het debuggen van grote sets.
Voorbeeld:
cargo test only_this_test
Een ontwikkelaar heeft tests naar een apart project verplaatst zonder toegang tot privé-functies en met een andere directorystructuur. Vanwege wijzigingen in de hoofdmodule begonnen de tests snel achter te lopen en hun relevantie te verliezen.
Voordelen: Testen kunnen worden geïsoleerd, waardoor ongewenste opname van testmodules in de release-build wordt voorkomen.
Nadelen: Verlies van synchronisatie, onmogelijkheid om interne details te testen, hoge onderhoudsbehoefte en risico op irrelevante tests.
Tests worden geschreven in dezelfde modules als de hoofdlogica, kleine atomische testcases testen de private en publieke API. Een nieuwkomer kan snel in de werking van de module worden ingewijd via de tests.
Voordelen: Maximale ondersteuning voor synchronisatie, transparantie, versnelde lokale testprocessen, geschikt voor test-gedreven ontwikkeling.
Nadelen: Toename van het aantal code-regels in de bronbestanden, potentiële complexiteitsgroei bij een groot aantal tests.