Skip to content

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.

Fortschritt0/7 Lektionen
60

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 bei cargo test
  • assert_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?

Editor wird geladen…
KI-Tutor (sokratisch — keine Komplettlösungen)

✅ Wichtige Erkenntnisse

0/3

Haken setzen, um deinen Lernfortschritt zu markieren:

61

Assertions: assert!, assert_eq! & Co.

Lektion 61 von 7

Die Assertions-Makros

  • assert!(bedingung) — schlägt fehl, wenn die Bedingung falsch ist
  • assert_eq!(links, rechts) — prüft Gleichheit; zeigt beide Werte beim Fehler
  • assert_ne!(links, rechts) — prüft Ungleichheit
  • panic!(...) — 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

0/3

Haken setzen, um deinen Lernfortschritt zu markieren:

62

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?

Editor wird geladen…
KI-Tutor (sokratisch — keine Komplettlösungen)

✅ Wichtige Erkenntnisse

0/3

Haken setzen, um deinen Lernfortschritt zu markieren:

63

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

0/3

Haken setzen, um deinen Lernfortschritt zu markieren:

64

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

0/3

Haken setzen, um deinen Lernfortschritt zu markieren:

65

Testläufe steuern & organisieren

Lektion 65 von 7

cargo test steuern

  • cargo test — führt alle Tests aus
  • cargo test name — führt nur Tests aus, deren Name name enthält
  • cargo test -- --test-threads=1 — Tests einzeln (nicht parallel)
  • cargo test -- --nocapture — zeigt println!-Ausgaben der Tests
  • cargo 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

0/3

Haken setzen, um deinen Lernfortschritt zu markieren:

66

Ü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:

  1. Schreibe eine Funktion maximum(zahlen: &[i32]) -> Option<i32>, die das größte Element liefert (oder None bei leerem Slice).
  2. Lege ein #[cfg(test)]-Modul mit Unit-Tests an:
    • leeres Slice → None
    • ein Element → dieses Element
    • mehrere Elemente → das größte
  3. Füge einen Test mit #[should_panic] hinzu, der prüft, dass eine absichtlich panickende Hilfsfunktion auch wirklich panickt.
  4. Verschiebe die Funktion in eine Library (lib.rs) und schreibe einen Integrationstest im tests/-Ordner, der maximum über die öffentliche API aufruft.
  5. Führe alles mit cargo test aus und bestätige, dass alle Tests grün sind.

Das verbindet #[test], Assertions, should_panic, Unit- und Integrationstests.

Test-WerkstattBereit
Editor wird geladen…

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?

Editor wird geladen…
KI-Tutor (sokratisch — keine Komplettlösungen)

✅ Wichtige Erkenntnisse

0/3

Haken setzen, um deinen Lernfortschritt zu markieren:

This site uses essential cookies for Stripe payments. No tracking cookies.