Modul 15 — Automatisiertes Testen
Tests schreiben, organisieren und ausführen: #[test], Assertions, should_panic, Unit- und Integrationstests. Lerne, wie du mit cargo test dein Programm absicherst und Fehler früh fängst.
Warum testen & cargo test
Lektion 60 von 7
Tests als Sicherheitsnetz
Ein Test ist eine Funktion, die prüft, dass sich dein Code so verhält, wie du es erwartest. Tests fangen Fehler bevor sie deine Nutzer erreichen — und sie erlauben dir, mutig umzubauen, ohne etwas kaputt zu machen.
Rust bringt das Test-Framework direkt mit: cargo test kompiliert dein
Projekt und führt alle Testfunktionen aus.
Dein erster Test
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}
#[test]markiert eine Funktion als Test#[cfg(test)]sagt: kompiliere dieses Modul nur beicargo testassert_eq!prüft auf Gleichheit; schlägt fehl, wenn nicht
cargo test meldet am Ende die Anzahl bestandener und fehlgeschlagener Tests.
cfg(test) hält Tests aus dem Build
#[cfg(test)] sorgt dafür, dass Test-Code nur beim Testen kompiliert wird —
nicht in deinem normalen cargo build oder cargo run. So blähen deine Tests
die ausgelieferte Binary nicht auf.
Praxis-Szenario: Die Preissumme
Nach einem Refactor rechnet der Shop falsche Summen — erst im Live-Betrieb aufgefallen. Die Chefin fordert: nie wieder. Du schlägst Unit-Tests für die Preissumme vor, inklusive der Randfälle.
Übung: Test einer Additionsfunktion
🧩 Übung m15-l60-e1
🔮 Vorhersage: Was passiert beim Ausführen?
✅ Wichtige Erkenntnisse
Haken setzen, um deinen Lernfortschritt zu markieren:
Assertions: assert!, assert_eq! & Co.
Lektion 61 von 7
Die Assertions-Makros
assert!(bedingung)— schlägt fehl, wenn die Bedingung falsch istassert_eq!(links, rechts)— prüft Gleichheit; zeigt beide Werte beim Fehlerassert_ne!(links, rechts)— prüft Ungleichheitpanic!(...)— bricht sofort ab (Test schlägt fehl)
#[test]
fn test_assertions() {
assert!(1 + 1 == 2);
assert_eq!(2 * 3, 6);
assert_ne!(5, 6);
}
Fehlerdiagnose verbessern
Bei assert_eq! zeigt Rust bei einem Fehler beide Werte. Für eigenen Text
hängt man eine Nachricht an:
#[test]
fn mit_nachricht() {
let wert = 3;
assert_eq!(wert, 4, "Wert {} war nicht 4", wert);
}
Warum assert_eq! die Debug-Ausgabe braucht
assert_eq! und assert_ne! benötigen, dass die Typen das Debug- und
PartialEq-Trait implementieren. Die meisten Standardtypen tun das bereits;
für eigene Structs leitest du sie mit #[derive(Debug, PartialEq)] ab.
✅ Wichtige Erkenntnisse
Haken setzen, um deinen Lernfortschritt zu markieren:
should_panic & Result in Tests
Lektion 62 von 7
Erwartete Panics testen
Manchmal soll Code bewusst panicken (z. B. bei ungültiger Eingabe). Mit
#[should_panic] markierst du einen Test, der nur besteht, wenn er panickt:
#[test]
#[should_panic(expected = "leer")]
fn panic_bei_leerem_input() {
let v: Vec<i32> = vec![];
if v.is_empty() {
panic!("Liste ist leer");
}
let _ = v[0];
}
expected prüft, dass die Panic-Nachricht den Text enthält — so fängst du
versehentliche Panics aus anderen Gründen ab.
Tests, die Result zurückgeben
Ein Test kann auch ein Result zurückgeben. Dann gilt: Ok(()) = bestanden,
Err(_) = fehlgeschlagen. Damit kannst du ? in Tests nutzen:
#[test]
fn mit_result() -> Result<(), String> {
let s = "42".parse::<i32>();
s.map(|_| ()).map_err(|e| format!("{e}"))
}
should_panic erfasst nur die erwartete Panic
#[should_panic(expected = "...")] stellt sicher, dass die Panic wirklich von
der erwarteten Stelle kommt. Ohne expected bestünde der Test auch, wenn
irgendwo anders — etwa in der Test-Vorbereitung — ein Panic auftritt.
Übung: Test einer Panik bei Division durch Null
🧩 Übung m15-l62-e1
🔮 Vorhersage: Was passiert beim Ausführen?
✅ Wichtige Erkenntnisse
Haken setzen, um deinen Lernfortschritt zu markieren:
Unit-Tests: nah am Code
Lektion 63 von 7
Unit-Tests: klein und isoliert
Unit-Tests prüfen einzelne Funktionen oder Module isoliert — ohne
Nebenwirkungen wie Dateien oder Netzwerk. Konventionell stehen sie in
derselben Datei wie der Code, in einem #[cfg(test)]-Modul:
pub fn addiere(a: i32, b: i32) -> i32 {
a + b
}
#[cfg(test)]
mod tests {
use super::*; // bringt addiere in den Scope
#[test]
fn test_addiere() {
assert_eq!(addiere(2, 3), 5);
}
}
Zugriff auf private Items
Weil das Test-Modul innerhalb der Datei liegt, kann es auch private Funktionen und Felder testen — ein Vorteil der Unit-Tests in derselben Datei.
use super::* bringt alles nach oben
use super::*; importiert alle Items des Elternmoduls (die Datei, in der
der Test steht) in den Test-Scope. So kannst du addiere(2, 3) direkt
aufrufen, statt super::addiere(2, 3) zu schreiben.
✅ Wichtige Erkenntnisse
Haken setzen, um deinen Lernfortschritt zu markieren:
Integrationstests: die öffentliche API
Lektion 64 von 7
Integrationstests: von außen
Integrationstests prüfen deine Library wie ein externer Nutzer — nur
über die öffentliche API. Sie liegen im tests/-Ordner, außerhalb von
src/:
projekt/
├── src/
│ └── lib.rs
└── tests/
└── integration_test.rs
// tests/integration_test.rs
use mein_crate::addiere;
#[test]
fn test_addiere_oeffentlich() {
assert_eq!(addiere(2, 3), 5);
}
Jede Datei im tests/-Ordner ist eine eigene Crate, die deine Library wie
ein Nutzer importiert. Du musst die Funktionen dafür pub machen.
Nur öffentliche API ist erreichbar
Integrationstests sehen nur die pub-Items deiner Library. Das ist
Absicht: Sie testen, was auch deine echten Nutzer verwenden. Private Details
bleiben unsichtbar — Unit-Tests sind dafür zuständig.
✅ Wichtige Erkenntnisse
Haken setzen, um deinen Lernfortschritt zu markieren:
Testläufe steuern & organisieren
Lektion 65 von 7
cargo test steuern
cargo test— führt alle Tests auscargo test name— führt nur Tests aus, deren Namenameenthältcargo test -- --test-threads=1— Tests einzeln (nicht parallel)cargo test -- --nocapture— zeigtprintln!-Ausgaben der Testscargo test -- --ignored— führt nur die ignorierten Tests aus
Tests markieren
Mit #[ignore] überspringst du einen Test bei cargo test (nützlich für
langsame Tests):
#[test]
#[ignore]
fn sehr_langsam() {
// teuer, nur bei Bedarf ausführen
}
Mit cargo test -- --ignored führst du nur diese aus.
Test-Threads verstehen
Standardmäßig laufen Tests parallel in mehreren Threads — schneller, aber
gemeinsamer Zustand (Dateien, globale Variablen) kann sich in die Quere kommen.
Mit --test-threads=1 laufen sie nacheinander, was bei geteilten Ressourcen
hilft.
✅ Wichtige Erkenntnisse
Haken setzen, um deinen Lernfortschritt zu markieren:
Übung: Eine Funktion absichern
Lektion 66 von 7
Projekt: Tests für eine kleine Utility-Funktion
Du sicherst eine Funktion mit Unit- und Integrationstests ab:
- Schreibe eine Funktion
maximum(zahlen: &[i32]) -> Option<i32>, die das größte Element liefert (oderNonebei leerem Slice). - Lege ein
#[cfg(test)]-Modul mit Unit-Tests an:- leeres Slice →
None - ein Element → dieses Element
- mehrere Elemente → das größte
- leeres Slice →
- Füge einen Test mit
#[should_panic]hinzu, der prüft, dass eine absichtlich panickende Hilfsfunktion auch wirklich panickt. - Verschiebe die Funktion in eine Library (
lib.rs) und schreibe einen Integrationstest imtests/-Ordner, dermaximumüber die öffentliche API aufruft. - Führe alles mit
cargo testaus und bestätige, dass alle Tests grün sind.
Das verbindet #[test], Assertions, should_panic, Unit- und
Integrationstests.
Rezepte aus diesem Modul
- Test markieren:
#[test]in einem#[cfg(test)]-Modul - Prüfen:
assert!,assert_eq!,assert_ne! - Panic erwarten:
#[should_panic(expected = "...")] - Von außen testen:
tests/-Ordner, eigene Crate - Ausführen:
cargo test
Jenseits von Unit-Tests: Formale Verifikation
Unit-Tests prüfen Beispiele — formale Verifikation beweist, dass kein Input das Programm brechen kann. Rusts Typsystem ist bereits ein leichtes Beweissystem: der Borrow-Checker beweist, dass kein Use-after-Free auftritt.
Aktuelle Forschung geht weiter:
- AutoVerus (2025): LLMs generieren automatisch formale Korrektheitsbeweise für Rust-Code — über 90 % von 150 Beweisaufgaben wurden gelöst.
- VerusBelt (2026): Eine formale semantische Grundlage für Verus, Rusts Beweis-orientierte Typ-Erweiterungen — mit Typ-Soundness-Theorem.
- Co-lozierte Tests (2026): Studien zeigen, dass die Struktur von Tests (ihre Nähe zum Code) die Qualität von KI-generiertem Code messbar beeinflusst.
Tests prüfen das, was du dir vorstellst. Verifikation prüft das, was du dir nicht vorstellst. Für sicherheitskritischen Code ist beides nötig.
Übung: maximum() absichern
🧩 Übung m15-l7-e1
🔮 Vorhersage: Was passiert beim Ausführen?
✅ Wichtige Erkenntnisse
Haken setzen, um deinen Lernfortschritt zu markieren: