Wie man Jasmine in eine Webanwendung integriert habe ich im Beitrag JavaScript Test mit Maven und Jasmine erläutert. Jedoch kann man das Testtool nicht nur für Webanwendungen, sondern auch für Webseiten mit PHP und JavaScript verwenden.
Für ein aktuelles Projekt habe ich alle meine JavaScript Methoden mit Jasmine Testfälle abgedeckt und konnte so mein Refactoring ganz entspannt entgegensehen.
Jasmine API / Dokumentation
Es gibt eine gute und ausführliche Dokumentation zu Jasmine unter http://jasmine.github.io/
Es gilt zu beachten das ab der Version 2.0 eine große Änderung an der API durchgeführt wurde somit ist eine Abwärtskompatibilität nicht mehr gegeben.
Download der Standalone Version
Es gibt verschiedene Möglichkeiten Jasmine in ein Projekt zu integrieren, bei einer „einfachen“ Webseite kann man auf die Standalone Version zurückgreifen welche unter https://github.com/jasmine/jasmine/releases zu finden ist.
Nachdem nun die Standalone Version heruntergeladen wurde kann dieses in ein Verzeichnis der Webseite entpackt werden. Die Verzeichnisstruktur könnte etwa so aussehen.
Sollten Sie eine andere Struktur bevorzugen, müssen ggf. Links zu Quellcodedateien angepasst werden.
Erster Start der mitgelieferten Testfälle
Der Standalone Version von Jasmine liegen eine Handvoll Testfälle bei, welche man mithilfe der Datei „SpecRunner.html“ aufrufen kann.
Eigene Testfälle Schreiben
Möchte man nun eigene Testfälle schreiben, so muss nur die Datei „SpecRunner.html“ erweitert werden.
Wenn man die Datei öffnet, so erkennt man an den vorhandenen Kommentaren was benötigt wird und wo die Testfälle abgelegt werden müssen / sollten.
<head> <meta charset="utf-8"> <title>Jasmine Spec Runner v2.3.4</title> <link rel="shortcut icon" type="image/png" href="lib/jasmine-2.3.4/jasmine_favicon.png"> <link rel="stylesheet" href="lib/jasmine-2.3.4/jasmine.css"> <script src="lib/jasmine-2.3.4/jasmine.js"></script> <script src="lib/jasmine-2.3.4/jasmine-html.js"></script> <script src="lib/jasmine-2.3.4/boot.js"></script> <!-- include source files here... --> <script src="src/Player.js"></script> <script src="src/Song.js"></script> <!-- include spec files here... --> <script src="spec/SpecHelper.js"></script> <script src="spec/PlayerSpec.js"></script> </head>
Wenn man auf die Source der Webseite verweisen möchte, muss man nur den Verzeichnisbaum nach unten navigieren. In meinem Beispiel sieht das folgendermaßen aus:
<!-- include source files here... --> <script src="../js/SimpleObject.js"></script>
Nun doch die Datei mit dem Testfall bekannt machen
<!-- include spec files here... --> <script src="spec/SimpleObjectSpec.js"></script>
Und schon kann unser Testset ausgeführt werden.
Aufbau eines Jasmine Testsets
Wie ein Testset aufgebaut ist, möchte ich kurz anhand des folgenden Beispiels erklären.
describe("SimpleObject", function() { var simpleObject; beforeEach(function() { simpleObject = new SimpleObject(); }); it("test method sayHello", function() { var result = simpleObject.sayHello("Stefan"); expect(result).toBe("Hello Stefan"); }); });
Globale Funktion „descripe“
Ein Testset beginnt immer mit dem Aufruf der globalen Jasmine Funktion „descripe“.
Diese Funktion bekommt 2 Parameter, ein String Wert für den Namen oder den Titel und eine Funktion welche die Testfälle beinhaltet (welche wiederum Funktionen beinhalten…)
Man man die Testfälle insich verschachteln so das eine Art Baumansicht der Testfälle entsteht.
describe("SimpleObject", function() { describe("Testset 1", function() { it("test 1", function() { }); it("test 2", function() { }); }); describe("Testset 2", function() { it("test 1", function() { }); it("test 2", function() { }); }); });
Globale Funktion „IT“
Die globale Funktion „it“ bekommt auch 2 Parameter welche wieder ein String Wert für den Namen bzw. für den Titel und eine Funktion wo bei man die Funktion weglassen kann und somit einen Test im Status „Pending“ erzeugt hat aber dazu später mehr.
Testfunktion „EXPECT“
Ein Prüfen des zu erwarteten Ergebnisses in einem Testfall wird mit der Funktion „expect“ und einem folgenden Matcher gelöst.
Prüfen auf gleichheit mit „toBe“
Mit dem Matcher „toBe“ können 2 Werte miteinander verglichen werden. Die Bedingung gilt als wahr, wenn beide Werte gleich sind.
(Bei Stringwerten wird auf Groß-/Kleinschreibung geachtet.)
expect(testValue.toUpperCase()).toBe("HELLO");
Prüfen auf Boolean.TRUE mit „.toBeTruthy“
Möchte man eine boolische Variable oder einen Ausdruck prüfen, so kann man mit
expect(testValueLength>4).toBeTruthy();
expect(testValueLength<5).toBeFalsy();
auf den den positiven Wert prüfen.
Ganze Objekte mit „.toEqual“ prüfen
Da man mit JavaScript auch OOP kann ist es manchmal sinnvoll ganze Objekte zu vergleichen.
it("test Objects equal", function() { var simpleObject1 = new SimpleObject(); var simpleObject2 = new SimpleObject(); expect(simpleObject1).toEqual(simpleObject2); });
Prüfen mit einer „ungleich“ Bedingung – „.not“.
Um eine Bedinung umzukehren, d.h. auf Boolean.FALSE oder ein Wert sollt ungleich einem anderen sein, muss nur das Wort „.not“ dem eigentlichen Matcher vorrangestellt werden.
expect(testValueLength<5).not.toBeTruthy();
Prüfen mit Regulären ausdrücken „.toMatch“
Was währen Testfälle ohne reguläre ausdrücke?
Es gibt nur 2 Arten wie man mit einem regulären Ausdruck eine Bedingung prüfen kann:
Erstens kann man den regulären ausdruck der Funktion „.toMatch“ mit einem Backslash umgeben übergeben, jedoch ohne Anführungszeichen.
expect(testValue).toMatch(/[^a-e]/);
Oder aber ohne die umgebenen Backslashes dafür jedoch mit Anführungszeichen.
expect(testValue).toMatch("[^a-e]");
Das Ergebnis ist jeweils das gleiche.
Zahlen prüfen
Das Prüfen von Zahlen miteinander kann wie folgt geschehen.
expect(pi).toBeGreaterThan(1); expect(pi).toBeLessThan(5);
Array auf Inhalt prüfen mit „.toContain“
Ein Array kann man ganz simple mit dem Matcher „.toContain“ auf seinen Inhalt prüfen.
var array = ["foo","bar"]; it("test for contains", function() { expect(array).toContain("bar"); expect(array).toContain("foo"); }); it("test for not contain", function() { expect(array).not.toContain("fo"); });
Ein Testfall / Testset deaktivieren
Möchte man ein bestehenden Testfall oder ein Testset deaktivieren so kann man dieses auf mehrere Arten lösen.
- „xit“ oder „xdescripe“
- Aufruf der Funktion pending();
xit("testValue length > 4", function() { var testValueLength = testValue.length; expect(testValueLength>4).toBeTruthy(); }); it("testValue length < 6", function() { var testValueLength = testValue.length; expect(testValueLength<5).not.toBeTruthy(); pending(); });
Beide Methoden führen dazu das der Testfall nicht ausgeführt wird, jedoch darf der Testfall keine syntaktischen Fehler enthalten.
Ein Testfall ohne Funktion definieren
Manchmal hat man Ideen für einen Testfall, welche man erstmal beschreiben möchte, aber noch nicht genau weiß wie dieser implementiert werden kann. Dafür kann man den Testfall „it“ anlegen, jedoch ohne den Funktionsparameter.
it("testValue length == 5");
Dieser Testfall wird als „pending“ erkannt und nicht ausgeführt.
Sollte man jedoch den Funktionsparameter ausprogrammieren aber jedoch leer lassen so bekommt man folgende Warnung beim Ausführen des Testsets
„SPEC HAS NO EXPECTATIONS“.
Oberflächentest mit Jasmine und jQuery
JavaScript Funktionen können nicht nur „Businesslogik“ erfüllen, sondern können auch die Oberfläche einer Webseite manipulieren, dafür kann es sinnvoll sein auch diese Aktionen zu testen.
Für diesen Zweck eignet sich das JavaScript Framework jQuery hervorragend da diese die Brücken zwischen den Browser unterschieden sehr gut schließt.
Einbinden von jQuery
Um jQuery in Jasmine Testfälle zu benutzen, muss die Datei „jasmine-jquery.js“ aus dem GitHub Repository „velesin/jasmine-jquery“ geladen werden.
<script src="../js/jquery-1.11.3.min.js"></script>
Erstellen einer Seite für die Oberflächentest
Da der Testfall für eine bestimmte Komponente einer Webseite ist, sollte diese Komponente welche den Testfall unterliegt in einer HTML Seite bereitgestellt werden.
Jasmine setzt hier auf eine vorgeschriebene Ordnerstruktur für die Ablage der Dateien.
Eine fixture Datei ist sehr minimalistisch aufgebaut, also ohne die Tags „html, head und body“ es werden „nur“ die HTML Komponenten implementiert, welche für den Test benötigt werden.
In meinem Fall sieht die Datei wiefolgt aus:
<div><h1>Fake Ajax Request</h1></div> <div id="output"></div>
Als „Beweis“ das unsere Datei korrekt geladen wurde kann man folgenden Testfall verwenden.
beforeEach(function() { loadFixtures('ajaxfixture.html'); }); it("test read title", function() { var titleTag = $("#title").text(); expect(titleTag).toBe("Fake Ajax Request"); });
Die Funktion „beforeEach“ führt vor jeder Ausführung eines Testfalls / Testsets eine definierte Aktion aus, in diesem Fall ist es das Laden der Fixture Datei.
Testen auf gesetzte CSS Klasse
Gegeben ist folgende Klasse welche die CSS Klasse einem HTML Objekt zuordnet.
var changeBorderColorExtern = function(element, style){ element.addClass(style); } function SimpleObject(){ this.changeBorderColor = changeBorderColorExtern; }
Wenn nun in dem Testfall das HTML Element durch die oben genannte Klasse manipuliert wird, können wir mithilfe „jasmine-jquery.js“ prüfen, ob die Styleklasse gesetzt wurde.
it("change element border color to red", function () { var element = $("#output"); var simpleObject = new SimpleObject(); simpleObject.changeBorderColor(element, "warning"); expect(element).toHaveClass('warning'); });
Cachen von HTML Objekten
Da wir nun jQuery verwenden können wir die benötigten HTML Elemente in Variablen“cachen“. Dieses wird durch die Zuweisung eines Elementes zu einer Variable gelöst.
var element = $("#output");
Der Hintergrund dieses Cachens von Elementen ist das bei jedem Aufruf von $(„#id“) der gesamte DOM nach dem Element durchsucht wird.
Download des Testprojektes für die eigene Verwendung.