Einleitung: Warum Unit-Tests nicht vor allem schützen
\n\nStellen Sie sich vor, Sie haben eine Funktion zum Sortieren eines Arrays geschrieben. Sie haben sie mit einem Dutzend Unit-Tests abgedeckt: leeres Array, ein Element, bereits sortierte Liste, umgekehrte Reihenfolge. Die Tests sind grün. Sie sind sich Ihres Codes sicher. Aber eine Woche später stürzt die Produktion ab – bei einem Array mit 1000 Elementen, Duplikaten und negativen Zahlen. Kommt Ihnen das bekannt vor?
\n\nTraditionelles Testen von TypeScript mit Beispielen (example-based testing) prüft nur die Szenarien, die Sie sich ausgedacht haben. Was aber, wenn ein Fehler in einer Kombination lauert, an die Sie nicht gedacht haben? Hier kommt das Property-basierte Testen (eigenschaftsbasiertes Testen) ins Spiel – ein Ansatz, der automatisch Tausende von zufälligen Eingabedaten generiert und die Invarianten Ihres Codes überprüft.
\n\nIn diesem Artikel schauen wir uns an, wie Sie Property-basiertes Testen in TypeScript mit der Bibliothek fast-check anwenden, welche Eigenschaften es zu prüfen gilt und wie dies den Entwicklungsansatz verändert.
\n\nWas ist Property-basiertes Testen?
\n\nAnstatt zu schreiben:
\nit('sortiert [3, 1, 2] → [1, 2, 3]', () => {\n expect(sort([3, 1, 2])).toEqual([1, 2, 3]);\n});\n\n\nBeschreiben Sie eine Eigenschaft (Property) – eine Aussage, die für alle korrekten Eingabedaten wahr sein muss:
\nit('ein sortiertes Array ändert die Reihenfolge der Elemente nicht', () => {\n fc.assert(\n fc.property(fc.array(fc.integer()), (arr) => {\n const sorted = sort(arr);\n // Eigenschaft 1: Die Länge bleibt erhalten\n expect(sorted.length).toBe(arr.length);\n // Eigenschaft 2: Jedes Element des ursprünglichen Arrays ist vorhanden\n // Eigenschaft 3: Das Array ist aufsteigend sortiert\n for (let i = 1; i < sorted.length; i++) {\n expect(sorted[i - 1]).toBeLessThanOrEqual(sorted[i]);\n }\n })\n );\n});\n\n\nDie Bibliothek fast-check generiert selbstständig Hunderte von zufälligen Arrays (einschließlich leere, mit NaN, mit Gleitkommazahlen, mit riesigen Werten) und führt Ihren Test mit jedem aus. Wenn auch nur einer fehlschlägt, erfahren Sie von dem Problem.
\n\nWarum ist das gerade in TypeScript wichtig?
\n\nTypeScript bietet statische Typisierung, aber diese garantiert nicht die Korrektheit der Logik. Property-basiertes Testen von TypeScript schließt die Lücke zwischen „die Typen passen“ und „der Code funktioniert richtig“. Besonders nützlich ist es für:
\n\n- \n
- Funktionen zur Datentransformation (map, filter, reduce) \n
- Parser und Validierer \n
- Algorithmen mit nicht-trivialer Logik \n
- Funktionen, die mit Grenzwerten arbeiten \n
Die Bibliothek fast-check: Schnellstart
\n\nInstallieren Sie fast-check in Ihrem Projekt:
\nnpm install --save-dev fast-check\n\n\nGrundlegende Syntax:
\nimport fc from 'fast-check';\n\ndescribe('Eigenschaften der Funktion reverse', () => {\n it('reverse(reverse(arr)) === arr', () => {\n fc.assert(\n fc.property(fc.array(fc.anything()), (arr) => {\n expect(reverse(reverse(arr))).toEqual(arr);\n })\n );\n });\n});\n\n\nHier ist fc.array(fc.anything()) ein Generator für beliebige Arrays. Sie können Generatoren kombinieren:
- \n
fc.integer()– ganze Zahlen \n fc.string()– Zeichenketten \n fc.record({ name: fc.string(), age: fc.integer({ min: 0, max: 120 }) })– Objekte \n fc.oneof(...)– zufällige Auswahl aus mehreren Generatoren \n
Welche Eigenschaften prüfen? 5 praktische Beispiele
\n\n1. Invarianz (Umkehrbarkeit)
\nJede Operation, die eine Umkehrung hat, sollte umkehrbar sein:
\nit('Serialisierung und Deserialisierung von JSON', () => {\n fc.assert(\n fc.property(fc.object(), (obj) => {\n const json = JSON.stringify(obj);\n const parsed = JSON.parse(json);\n expect(parsed).toEqual(obj);\n })\n );\n});\n<