Rust wurde ursprünglich mit dem Schwerpunkt auf Zuverlässigkeit und Sicherheit entwickelt, weshalb Modultests ein integraler Bestandteil der Spracheko-system sind. Tests sind auf Sprachebene und im Toolchain (cargo test) integriert, was sie zu einem organischen Teil des Entwicklungsprozesses macht.
Hintergrund
In traditionellen Ökosystemen (z.B. C/C++, Python, Java) existierten Tests unabhängig vom Programm selbst. In Rust sind Tests Teil des Codes; sie werden als vollständiges Modul kompiliert und überprüft. Diese Synergie wird durch Sprachkonstrukte und Compilerbesonderheiten erreicht.
Problem
Ohne angemessene Tests kann die Zuverlässigkeit wichtiger Funktionen nicht garantiert werden. Bei komplexen und modularen Projekten entsteht oft die Frage, wie man Tests bequem organisiert, um zu vermeiden, dass sie vom Zustand anderer Module abhängen, ohne die Struktur des Projekts zu komplizieren.
Lösung
In Rust werden Tests innerhalb der Quellcodedatei (mittels #[cfg(test)]) oder in einem separaten Ordner „tests“ für Integrationstests platziert. Jeder Modul kann private Tests hinzugefügt werden, die Zugriff auf die private API haben.
Beispielcode:
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); } }
Wichtige Merkmale:
Wozu dient use super::*; im Testmodul?
Damit Tests auf Funktionen und Strukturen des aktuellen Moduls (einschließlich privater) zugreifen können, wird in Tests normalerweise use super::*; verwendet.
Kann #[test] asynchron sein?
Standardmäßig unterstützt #[test] im Sprache keine asynchronen Funktionen, aber durch externe Crates (z.B. tokio oder async-std) können asynchrone #[test] erstellt werden.
Beispielcode:
#[tokio::test] async fn test_async_add() { assert_eq!(add(2, 2).await, 4); }
Können Tests den globalen Zustand ändern und wie kann man das vermeiden?
Tests in Rust werden standardmäßig parallel ausgeführt; daher kann globaler, nicht synchronisierter Zustand (static mut) nicht verwendet werden, da sonst Rennbedingungen entstehen.
Alle Tests verwenden eine globale Variable:
static mut COUNTER: u32 = 0; #[test] fn test_inc() { unsafe { COUNTER += 1; } }
Vorteile:
Nachteile:
Jeder Test ist isoliert, verwendet lokale Variablen und Mock-Objekte.
Vorteile:
Nachteile: