Property-basiertes Testen in Rust: Wie Sie Bugs fangen, die Sie nicht gesucht haben

Online Python-Trainer für Anfänger

Lernen Sie Python einfach ohne theoretische Überlastung. Lösen Sie praktische Aufgaben mit automatischer Überprüfung und schreiben Sie Code direkt im Browser.

Kurs starten

Property-basiertes Testen in Rust: Die Kunst, Bugs zu fangen, die Sie nicht gesucht haben

\n\n

Haben Sie jemals Unit-Tests geschrieben, sie ausgeführt, alles war grün, und dann ist die Anwendung bei der ersten ungewöhnlichen Eingabe eines Benutzers abgestürzt? Kommt Ihnen bekannt vor. Klassische Tests sind wie zu prüfen, ob ein Schloss funktioniert, indem man nur den eigenen Schlüssel hineinsteckt. Was aber, wenn ein Angreifer (oder einfach nur der Zufall) einen Schraubenzieher, eine Büroklammer oder ein Stück Draht mitbringt?

\n\n

In Rust, einer Sprache, die für ihre Sicherheit bekannt ist, gibt es ein mächtiges Werkzeug, mit dem man nicht nur bestimmte Szenarien überprüfen, sondern beweisen kann, dass sich der Code für alle möglichen Eingabedaten korrekt verhält. Das ist Property-basiertes Testen. Und heute werden wir es praktisch mit echten Beispielen durchgehen, damit Sie dieses Wissen sofort in Ihren Projekten anwenden können.

\n\n

Was ist Property-basiertes Testen und worin unterscheidet es sich vom herkömmlichen Testen?

\n\n

Stellen Sie sich vor, Sie testen eine Sortierfunktion. Ein gewöhnlicher Unit-Test sieht so aus:

\n\n
fn test_sort() {\n    let mut arr = vec![3, 1, 2];\n    arr.sort();\n    assert_eq!(arr, vec![1, 2, 3]);\n}
\n\n

Funktioniert es? Ja. Aber was, wenn das Array bereits sortiert ist? Was, wenn es leer ist? Was, wenn es 10.000 Elemente in umgekehrter Reihenfolge enthält? Für jeden Fall manuell Tests zu schreiben, ist ein Weg ins Unendliche.

\n\n

Property-basiertes Testen dreht den Ansatz um. Anstatt konkrete Eingabedaten vorzugeben, beschreiben Sie eine Eigenschaft (Property), die für jede korrekte Eingabe gelten muss. Eine spezielle Bibliothek (z. B. proptest oder quickcheck) generiert dann selbstständig hunderte und tausende zufälliger Testfälle und versucht, den einen zu finden, der Ihre Eigenschaft verletzt.

\n\n

Für die Sortierung könnte die Eigenschaft lauten: „Das Ergebnis der Sortierung hat immer die gleiche Länge wie das ursprüngliche Array, und jedes Element des ursprünglichen Arrays ist genau einmal im Ergebnis enthalten.“ Oder einfacher: „Das Ergebnis ist aufsteigend sortiert.“

\n\n

Warum Rust die ideale Umgebung für Property-basiertes Testen ist

\n\n

Rust fängt bereits viele Fehler zur Kompilierzeit ab (Speicherverwaltung, Datenrennen). Aber es gibt eine Klasse von Bugs, die der Compiler nicht sieht: logische Fehler, fehlerhafte Behandlung von Randfällen, unerwartetes Verhalten bei spezifischen Datenkombinationen. Hier kommt das Property-basierte Testen in Rust ins Spiel.

\n\n

Es ergänzt das Typsystem perfekt: Typen garantieren, dass die Daten die richtige Struktur haben, und Property-basierte Tests stellen sicher, dass die Logik zur Verarbeitung dieser Daten für den gesamten Raum möglicher Werte korrekt ist.

\n\n

Werkzeuge: proptest vs. quickcheck

\n\n

Im Rust-Ökosystem gibt es zwei Haupt-Frameworks: proptest und quickcheck. Beide tun im Wesentlichen dasselbe, aber mit unterschiedlicher Philosophie.

\n\n
    \n
  • quickcheck ist älter und minimalistischer. Es arbeitet über das Arbitrary-Trait. Sie schreiben eine Funktion, die zufällige Daten entgegennimmt, und der Test wird mehrfach ausgeführt.
  • \n
  • proptest ist modern, leistungsstark und bietet eine Vielzahl von Strategien zur Datengenerierung. Es erlaubt eine feine Steuerung, welche Daten generiert werden, und kann ein fehlschlagendes Beispiel auf den minimal reproduzierbaren Fall „verkleinern“ (shrink). Es ist der heutige Goldstandard.
  • \n
\n\n

Wir werden proptest verwenden, da es mehr Kontrolle bietet und besser dokumentiert ist. Installieren wir es:

\n\n
# Cargo.toml\n[dev-dependencies]\nproptest = "1.4"
\n\n

Unser ersten Property-basierten Test in Rust schreiben

\n\n

Kehren wir zu unserer Sortierung zurück. Schreiben wir einen Test, der prüft, ob das Ergebnis tatsächlich sortiert ist:

\n\n
use proptest::prelude::*;\n\nfn is_sorted<T: Ord>(slice: &[T]) -> bool {\n    slice.windows(2).all(|w| w[0] <= w[1])\n}\n\nproptest!                

Blogs

Buchempfehlungen