Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Kapitel 18: Testautomatisierung und Dokumentation – Die Qualitätskontrolle

Stell dir vor, du leitest eine Spielzeugfabrik, die kleine ferngesteuerte Rennautos herstellt.

Bevor ein Auto in den bunten Karton gepackt und an Kunden verschickt wird, muss es auf Herz und Nieren geprüft werden. Dazu hast du am Ende der Produktionslinie einen kleinen Prüfstand aufgebaut. Ein Mitarbeiter setzt das Auto auf eine Teststrecke und prüft:

  1. Fährt das Auto vorwärts, wenn man den Hebel drückt?
  2. Funktionieren die Bremsen?
  3. Leuchten die Scheinwerfer?

Dieser Prüfstand befindet sich in der Werkstatt. Die Kunden im Laden bekommen ihn niemals zu Gesicht. Sie kaufen nur das fertige Auto. Aber ohne diesen Prüfstand hättest du keine Ahnung, ob manche Autos defekt ausgeliefert werden.

In der Programmierung ist das exakt dasselbe. Um sicherzustellen, dass dein Code fehlerfrei arbeitet (und auch nach zukünftigen Änderungen nicht kaputtgeht), schreiben wir Tests.

Zusätzlich legen wir dem Auto eine Bedienungsanleitung bei. In Rust schreiben wir diese Anleitung direkt in den Code, und Cargo baut daraus automatisch eine schicke Website für unsere Anwender.


1. Lernziele – Das wirst du heute lernen

  • Warum wir testen: Du verstehst die Bedeutung von automatischen Qualitätsprüfungen.
  • Unit-Tests schreiben: Du erstellst Testfunktionen mit der Annotation #[test].
  • Die Asserts anwenden: Du prüfst Werte mit assert_eq! und assert!.
  • Das Testmodul einrichten: Du nutzt #[cfg(test)], um Testcode vom fertigen Programm zu trennen.
  • Dokumentationen verfassen: Du schreibst verständliche Anleitungen direkt mit ///.

2. Der erste Unit-Test

In Rust schreiben wir Tests als ganz normale Funktionen, die wir mit dem Attribut #[test] kennzeichnen. Wenn wir im Terminal cargo test ausführen, sucht Cargo nach all diesen Funktionen und führt sie aus.

Lass uns eine einfache Funktion zum Addieren testen:

#![allow(unused)]
fn main() {
// Die Funktion, die wir prüfen wollen
pub fn addiere(a: i32, b: i32) -> i32 {
    a + b
}

// Wir erstellen ein spezielles Test-Modul.
// #[cfg(test)] sagt dem Compiler: "Kompiliere dieses Modul NUR, wenn wir 'cargo test' ausführen!"
// Wenn wir die App normal bauen (cargo build), wird dieses Modul komplett ignoriert.
#[cfg(test)]
mod tests {
    // Wir importieren die Funktion 'addiere' aus dem übergeordneten Modul
    use super::*;

    // #[test] macht diese Funktion zu einer Testfunktion
    #[test]
    fn test_addiere_positiv() {
        // assert_eq! prüft, ob beide Seiten identisch sind (eq = equal)
        assert_eq!(addiere(2, 2), 4);
    }

    #[test]
    fn test_addiere_negativ() {
        assert_eq!(addiere(-2, -3), -5);
    }
}
}

Die wichtigsten Zusicherungen (Asserts)

Um Werte zu prüfen, bietet uns Rust drei Makros:

  • assert!(bedingung): Der Test besteht, wenn die Bedingung true ergibt.
  • assert_eq!(a, b): Der Test besteht, wenn a gleich b ist.
  • assert_ne!(a, b): Der Test besteht, wenn a ungleich b ist (ne = not equal).

Wir können jedem dieser Makros eine eigene Fehlermeldung mitgeben:

#![allow(unused)]
fn main() {
assert_eq!(addiere(2, 2), 4, "Oh je! 2 + 2 war nicht gleich 4!");
}

3. Dokumentation für Anwender schreiben

Wenn andere Programmierer deinen Code verwenden sollen, brauchen sie eine Anleitung. In Rust schreiben wir Dokumentationen mit drei Schrägstrichen ///.

#![allow(unused)]
fn main() {
/// Multipliziert zwei Zahlen miteinander.
///
/// # Beispiele
///
/// ```
/// let ergebnis = multipliziere(3, 4);
/// assert_eq!(ergebnis, 12);
/// ```
pub fn multipliziere(a: i32, b: i32) -> i32 {
    a * b
}
}

Wenn du nun im Terminal des Projekts den Befehl:

cargo doc --open

ausführst, liest Cargo diese Kommentare, übersetzt das Markdown-Format in HTML und öffnet automatisch eine professionelle Dokumentations-Website in deinem Browser!


4. Compilerfehler-Show: Debug-Traits vergessen

Ein häufiger Fehler bei Anfängern betrifft die Nutzung von assert_eq! auf eigenen Strukturen.

#![allow(unused)]
fn main() {
struct Punkt {
    x: i32,
    y: i32,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_punkt() {
        let p1 = Punkt { x: 1, y: 2 };
        let p2 = Punkt { x: 1, y: 2 };
        assert_eq!(p1, p2); // Compilerfehler!
    }
}
}

Die Fehlermeldung des Compilers:

error[E0277]: can't compare `Punkt` with `Punkt`
  --> src/main.rs:14:9
   |
14 |         assert_eq!(p1, p2);
   |         ^^^^^^^^^^^^^^^^^^ no implementation for `Punkt == Punkt`
   |
   = help: the trait `PartialEq` is not implemented for `Punkt`

Die Erklärung:

Das Makro assert_eq! muss im Erfolgsfall wissen, ob zwei Objekte gleich sind. Schlägt der Test fehl, muss es die Objekte zudem auf der Konsole ausgeben können. Daher müssen alle Typen, die mit assert_eq! verglichen werden, zwei Eigenschaften (Traits) besitzen: PartialEq und Debug.

Die Lösung: Bitte den Compiler über das derive-Attribut, diese Traits automatisch für dich zu erstellen:

#![allow(unused)]
fn main() {
#[derive(PartialEq, Debug)] // Hinzufügen der benötigten Hilfs-Traits
struct Punkt {
    x: i32,
    y: i32,
}
}

5. Zusammenfassung

  1. Tests sichern die Qualität deines Codes und verhindern zukünftige Fehler.
  2. Das Attribut #[test] kennzeichnet Testfunktionen.
  3. Über assert_eq!, assert_ne! und assert! prüfen wir Ergebnisse.
  4. Mit #[cfg(test)] kapseln wir das Testmodul, damit es nicht in der finalen Binärdatei landet.
  5. Dokumentationen schreiben wir mit /// direkt im Code und generieren sie mit cargo doc.