Specification-driven Testing

JSCheck im Check
Kommentare

Die Welt der Entwicklungswerkzeuge für JavaScript ist immer in Bewegung. So wundert es nicht, dass nahezu jeden Tag neue Tools zur Qualitätssicherung entstehen und auch wieder verschwinden. Das ist natürlich zum einen dem Umstand geschuldet, dass JavaScript im Vergleich zu seinen Brüdern und Schwestern eine recht junge Sprache ist, zum anderen aber auch der schieren Masse an Entwicklern, die sich tagtäglich mit dieser Sprache auseinandersetzen. Einer von ihnen ist Douglas Crockford, ein JavaScript-Urgestein. Bekannt geworden ist er durch die Entdeckung der guten Seiten von JavaScript („JavaScript: The Good Parts“) sowie durch seinen Syntax-Checker jslint [1] und die Erfindung des JSON-Formats [2], das in nahezu jeder modernen Webapplikation Einsatz findet. Vor Kurzem veröffentlichte Crockford ein weiteres Werkzeug, um die Qualität von JavaScript-Projekten zu verbessern: JSCheck [3]

Crockford bezeichnet JSCheck als ein spezifikationsgetriebenes Testwerkzeug. Im Vergleich zu üblichen Unit Tests, in denen ein fester Testrahmen vorgegeben wird, ist die Idee hinter JSCheck die Spezifikation von Parametern, innerhalb derer eine Methode oder ein Objekt zu funktionieren hat. Auf dieser Basis werden anschließend automatisiert konkrete Testfälle erzeugt und ausgeführt. 

Ein Beispiel: relative Zeit

Der Einsatzbereich sowie die Vorteile von JSCheck gegenüber herkömmlichen Unit Tests sollen an einem Beispiel verdeutlicht werden: Vor kurzer Zeit schrieb ich eine einfache Funktion, die mir aus zwei Datumsangaben einen relativen Wert berechnet und als Zeichenkette formatiert: 30 seconds ago, 12 minutes ago, 6 days ago etc. In der freien Wildbahn ist diese Funktionalität am häufigsten in sozialen Netzwerken wie Facebook, Twitter und Co. anzutreffen. Listing 1 zeigt eine vereinfachte Variante der Funktion ago, die diese Berechnung ausführt.

function ago(time, fixture) {
    var distance = time.getTime() / 1000 - fixture.getTime() / 1000;
    
    switch(true) {
        case distance < 0:
            return 'In the future';
        case distance < 60:
            return "" + Math.floor(distance) + " seconds ago";
        case distance < 60 * 60:
            return "" + Math.floor(distance / 60) + " minutes ago";
        case distance < 60 * 60 * 24:
            return "" + Math.floor(distance / 60 / 60) + " hours ago";
        default:
            return "long ago"
    }
}  

Als Eingabe erhält die Funktion zwei JavaScript-Date-Objekte: den Zeitpunkt, für den die relative Berechnung erfolgen soll (time), und den Zeitpunkt, der als Ausgangspunkt für die Berechnung verwendet wird (fixture). In den meisten Einsatzfällen handelt es sich hierbei um die Jetzt-Zeit (Date.now()). Zu Beginn der Funktion wird die Distanz der beiden Zeiten in Sekunden bestimmt. Da ein JavaScript-Date-Objekt immer auf Millisekunden operiert, wird der Wert durch 1000 dividiert. Der so entstandene Wert dient als Grundlage für alle weiteren Operationen, die durch das switch-case-Statement in Zeile 4 abgearbeitet werden. Hier wird auf die unterschiedlichsten Wertebereiche getestet und eine entsprechende Zeichenkette zurückgeliefert: Ist die Distanz negativ, so liegt das gegebene Datum in der Zukunft. Ist die Distanz kleiner als 60, so befinden wir uns in einem Bereich von Sekunden. Ist die Distanz kleiner als 3600, befinden wir uns in einem Bereich von Minuten. Es zeichnet sich schnell ein Muster ab, das entsprechend fortgesetzt wird. Zum Schluss wird für den Fall, dass keine der definierten Detailstufen ausreicht, die Zeichenkette long ago zurückgegeben. 

JSCheck 

Um sicherzustellen, dass die ago-Funktion korrekt arbeitet, werden mittels JSCheck Tests dafür erzeugt. In einem ersten Schritt wird JSCheck heruntergeladen. Der gesamte Quellcode des Tools befindet sich in einer Datei namens jscheck.js, die auf den Github-Seiten [4] des Projekts verfügbar ist. Nach dem Herunterladen kann diese einfach mittels Script-Tag im Browser geladen werden. In vielen Fällen ist eine Ausführung auf der Kommandozeile, zum Beispiel mit Node.js, wünschenswert. Hierfür muss die folgende Zeile ans Ende der Datei angefügt werden: 

(typeof window === 'undefined' ? exports : window).JSC = JSC;.  

Im Browser völlig harmlos, erlaubt diese Zeile die Verwendung mit dem Modulsystem von Node.js. 

Quelle Aufmacherbild: Tools for building on the table via Shutterstock / Urheberrecht: FotoYakov

[ header = JSC + Fazit ]

JSC

Sämtliche Funktionen von JSCheck stehen über das JSC-Objekt zum Aufruf bereit. Eine der wichtigsten ist JSC.claim, die dazu dient, neue Testspezifikationen zu verfassen. Die Funktion nimmt vier Argumente entgegen: einen Testnamen, eine Aussagefunktion und ein Array von Parameterspezifikationen. Das vierte Argument ist optional und kann eingesetzt werden, um eine Klassifizierungsfunktion zu übergeben. 

Listing 2 zeigt einen ersten Test für die ago-Funktion, der mittels JSC.claim definiert wird. Das erste Argument gibt dem Test seinen Namen, Correct relative time. Das zweite Argument ist eine Funktion, die in diesem Beispiel drei Argumente entgegennimmt: verdict, time und fixture. Das dritte Argument ist ein Array, das zwei Integer-Werte zwischen 0 und 127800 als Parameter definiert. 

JSC.claim(
    "Correct relative time",
    function(verdict, time, fixture) {
        var distance = time - fixture;

        var relative = ago(
            new Date(time*1000),
            new Date(fixture*1000)
        );

        if (distance < 0) {
            return verdict(/future/.test(relative));
        }
    },
    [
        JSC.integer(86400*2),
        JSC.integer(86400*2)
    ]
); 

Die als zweites Argument übergebene Funktion besitzt wiederum selber Argumente, die sich wie folgt zusammensetzen: Das erste Argument ist für jede Ausführung identisch. Es kann nicht beeinflusst werden und enthält immer eine Funktion. Diese Funktion muss mit true oder false aufgerufen werden, um JSCheck mitzuteilen, ob ein Test erfolgreich war oder nicht. Die Anzahl aller weiteren Argumente ergibt sich aus der Anzahl der Parameterspezifikationen. Hier werden zufällig Werte innerhalb der spezifizierten Grenzen und vom spezifizierten Typ übergeben, in diesem Fall also zwei Integer-Werte, die im Bereich zwischen 0 und 172800 liegen (vgl. Zeile 16 bis 17). Es gibt diverse Parameterspezifikationen in JSCheck: für Zeichenketten, einzelne Zeichen, Arrays, Objekte und vieles mehr. All diese werden detailliert in der Dokumentation erklärt. 

Der Inhalt der Aussagefunktion stellt einen einfach beschriebenen Ablauf dar. Zunächst wird die Distanz der beiden Integer-Werte berechnet. Anschließend wird die zuvor gezeigte ago-Funktion auf der Basis dieser beiden Werte aufgerufen. Zum Schluss wird ein verdict (Urteil) über alle Eingabewerte gefällt, bei denen die Distanz kleiner als 0 ist. In diesem Fall wird geprüft, ob in dem generierten String das Wort „future“ vorkommt. Wird der auf diese Weise definierte Test mittels JSC.check() ausgeführt, ergibt sich eine Ausgabe ähnlich Listing 3. 49 Tests waren erfolgreich, 51 werden jedoch als lost geführt. 

Correct relative time: 100 cases tested, 49 pass, 51 lost
Total pass 49, lost 51

Hierbei handelt es sich um Tests, für die nie die verdict-Funktion aufgerufen wurde. Verständlich, denn aktuell tut die Testfunktion das nur für Distanzen, die kleiner als 0 sind. Durch Hinzufügen der weiteren Distanzen (Listing 4) lässt sich das leicht beheben und der Test vervollständigen. 

...
if (distance < 0) {
    return verdict(/future/.test(relative));
}
if (distance < 60) {
    return verdict(/seconds/.test(relative));
}
if (distance < 3600) {
    return verdict(/minutes/.test(relative));
}
if (distance < 86400) {
    return verdict(/hours/.test(relative));
}
return verdict(/long ago/.test(relative));
... 

Fazit 

Dieser Artikel hat nur an der Oberfläche von JSCheck gekratzt und dennoch einige interessante Aspekte aufgezeigt. JSCheck ist kein Unit-Testwerkzeug im herkömmlichen Sinn. Es ist vielmehr eine Möglichkeit, spezifizierte Grenzen von bestimmten Funktionen zu testen. Durch die relativ lose Definition dieser Grenzen ist die Wahrscheinlichkeit recht hoch, dass bei mehreren Testdurchläufen Randfälle einer Funktion überprüft werden, die vielleicht beim Schreiben von Unit Tests übersehen worden wären. JSCheck ist mehr als Ergänzung zu Qualitätssicherungsmaßnahmen eines Projekts zu sehen, denn als Alternative. Unit Tests sollten niemals für den Einsatz von JSCheck geopfert werden. Dennoch stellt dieses neue Werkzeug eine interessante Möglichkeit dar, um Randbedingungen schnell und einfach zu überprüfen. Ich freue mich bereits darauf, es in Zukunft häufiger zu verwenden. 

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -