1. Code
  2. JavaScript

So testen Sie Ihren JavaScript-Code mit QUnit

QUnit wurde vm jQuery-Team entwickelt und ist ein hervorragendes Framework für Unit-Tests Ihres JavaScript. In diesem Tutorial werde ich vorstellen, was QUnit speziell ist und warum Sie sich darum kümmern sollten, Ihren Code gründlich zu testen.
Scroll to top
11 min read

German (Deutsch) translation by Władysław Łucyszyn (you can also view the original English article)

QUnit wurde vm jQuery-Team entwickelt und ist ein hervorragendes Framework für Unit-Tests Ihres JavaScript. In diesem Tutorial werde ich vorstellen, was QUnit speziell ist und warum Sie sich darum kümmern sollten, Ihren Code gründlich zu testen.

Was ist QUnit?

QUnit ist ein leistungsstarkes JavaScript-Framework für Unit-Tests, mit dem Sie Code debuggen können. Es wurde von Mitgliedern des jQuery-Teams geschrieben und ist die offizielle Testsuite für jQuery. QUnit ist jedoch allgemein genug, um normalen JavaScript-Code zu testen, und es kann sogar serverseitiges JavaScript über eine JavaScript-Engine wie Rhino oder V8 testen.

Machen Sie sich keine Sorgen, wenn Sie mit der Idee des "Unit-Tests" nicht vertraut sind. Es ist nicht zu schwer zu verstehen:

In der Computerprogrammierung ist Unit Testing eine Software-Verifizierungs- und Validierungsmethode, bei der ein Programmierer testet, ob einzelne Einheiten des Quellcodes für die Verwendung geeignet sind. Eine Einhit ist der kleinste testbare Teil einer Anwendung. Bei der prozeduralen Programmierung kann eine Einheit eine einzelne Funktion oder Prozedur sein.

Dies wird aus Wikipedia zitiert. Einfach ausgedrückt, Sie schreiben Tests für jede Funktionalität Ihres Codes. Wenn alle diese Tests bestanden sind, können Sie sicher sein, dass der Code fehlerfrei ist (hängt hauptsächlich davon ab, wie gründlich Ihre Tests sind).

Warum sollten Sie Ihren Code testen?

Wenn Sie noch keine Komponententests geschrieben haben, wenden Sie Ihren Code wahrscheinlich direkt auf eine Website an, klicken Sie eine Weile, um festzustellen, ob ein Problem auftritt, und versuchen Sie, es zu beheben, sobald Sie eines finden. Es gibt viele Probleme mit dieser Methode.

Erstens ist es sehr langweilig. Das Klicken ist eigentlich keine leichte Aufgabe, da Sie sicherstellen müssen, dass alles angeklickt wird und es sehr wahrscheinlich ist, dass Sie ein oder zwei Dinge verpassen. Zweitens ist alles, was Sie zum Testen getan haben, nicht wiederverwendbar, was bedeutet, dass es nicht einfach ist, Regressionen zu finden. Was ist eine Regression? Stellen Sie sich vor, Sie haben Code geschrieben und getestet, alle gefundenen Fehler behoben und veröffentlicht. Aschließend sendet ein Benutzer Feedback zu neuen Fehlern und fordert einige neue Funktionen an. Sie kehren zum Code zurück, beheben diese neuen Fehler und fügen diese neuen Funktionen hinzu. Was als nächstes passieren könnte, ist, dass einige der alten Fehler wieder auftauchen, die als "Regressionen" bezeichnet werden. Sehen Sie, jetzt müssen Sie erneut klicken, und es besteht die Möglichkeit, dass Sie diese alten Fehler nicht wiederfinden. Selbst wenn Sie dies tun, wird es eine Weile dauern, bis Sie herausfinden, dass das Problem durch Regressionen verursacht wird. Beim Komponententest schreiben Sie Tests, um Fehler zu finden, und sobald der Code geändert wurde, filtern Sie ihn erneut durch die Tests. Wenn eine Regression auftritt, werden einige Tests definitiv fehlschlagen, und Sie können sie leicht erkennen, wenn Sie wissen, welcher Teil des Codes den Fehler enthält. Da Sie wissen, was Sie gerade geändert haben, kann es leicht behoben werden.

Ein weiterer Vorteil von Unit-Tests besteht insbesondere in der Webentwicklung: Sie erleichtern das Testen der Cross-Browser-Kompatibilität. Führen Sie Ihre Tests einfach in verschiedenen Browsern aus. Wenn ein Problem in einem Browser auftritt, beheben Sie es und führen Sie diese Tests erneut aus, um sicherzustellen, dass keine Regression in anderen Browsern auftritt. Sie können sicher sein, dass alle Zielbrowser unterstützt werden, sobald alle die Tests bestanden haben.

Ich möchte eines von John Resigs Projekten erwähnen: TestSwarm. Das Testen von JavaScript-Einheiten wird auf ein neues Niveau gebracht, indem es verteilt wird. Es ist eine Website, die viele Tests enthält. Jeder kann dorthin gehen, einige der Tests ausführen und das Ergebnis dann an den Server zurückgeben. Auf diese Weise kann Code sehr schnell in verschiedenen Browsern und sogar auf verschiedenen Plattformen getestet werden.

So schreiben Sie Unit-Tests mit QUnit

Wie schreibt man Unit-Tests mit QUnit genau? Zunächst müssen Sie eine Testumgebung einrichten:

1
<!DOCTYPE html>
2
<html>
3
<head>
4
	<title>QUnit Test Suite</title>
5
	<link rel="stylesheet" href="http://github.com/jquery/qunit/raw/master/qunit/qunit.css" type="text/css" media="screen">
6
	<script type="text/javascript" src="http://github.com/jquery/qunit/raw/master/qunit/qunit.js"></script>
7
	<!-- Your project file goes here -->
8
	<script type="text/javascript" src="myProject.js"></script>
9
	<!-- Your tests file goes here -->
10
	<script type="text/javascript" src="myTests.js"></script>
11
</head>
12
<body>
13
	<h1 id="qunit-header">QUnit Test Suite</h1>
14
	<h2 id="qunit-banner"></h2>
15
	<div id="qunit-testrunner-toolbar"></div>
16
	<h2 id="qunit-userAgent"></h2>
17
	<ol id="qunit-tests"></ol>
18
</body>
19
</html>

Wie Sie sehen können, wird hier eine gehostete Version des QUnit-Frameworks verwendet.

Der Code, der getestet werden soll, sollte in myProject.js eingefügt werden, und Ihre Tests sollten in myTests.js eingefügt werden. Um diese Tests auszuführen, öffnen Sie einfach diese HTML-Datei in einem Browser. Jetzt ist es Zeit, einige Tests zu schreiben.

Die Bausteine von Unit-Tests sind Aussagen.

Eine Zusicherung ist eine Aussage, die das zurückkehrende Ergebnis Ihres Codes vorhersagt. Wenn die Vorhersage falsch ist, ist die Behauptung fehlgeschlagen, und Sie wissen, dass etwas schief gelaufen ist.

Um Behauptungen auszuführen, sollten Sie sie in einen Testfall einfügen:

1
// Let's test this function

2
function isEven(val) {
3
	return val % 2 === 0;
4
}
5
6
test('isEven()', function() {
7
	ok(isEven(0), 'Zero is an even number');
8
	ok(isEven(2), 'So is two');
9
	ok(isEven(-4), 'So is negative four');
10
	ok(!isEven(1), 'One is not an even number');
11
	ok(!isEven(-7), 'Neither is negative seven');
12
})

Hier haben wir eine Funktion definiert, isEven, die erkennt, ob eine Zahl gerade ist, und wir möchten diese Funktion testen, um sicherzustellen, dass sie keine falschen Antworten zurückgibt.

Wir rufen zuerst test() auf, der einen Testfall erstellt. Der erste Parameter ist eine Zeichenfolge, die im Ergebnis angezeigt wird, und der zweite Parameter ist eine Rückruffunktion, die unsere Aussagen enthält. Diese Rückruffunktion wird aufgerufen, sobald QUnit ausgeführt wird.

Wir haben fünf Behauptungen geschrieben, die alle boolesch sind. Eine boolesche Zusicherung erwartet, dass ihr erster Parameter wahr ist. Der zweite Parameter ist ebenfalls eine Meldung, die im Ergebnis angezeigt wird.

Folgendes erhalten Sie, sobald Sie den Test ausgeführt haben:

a test for isEven()a test for isEven()a test for isEven()

Da alle diese Behauptungen erfolgreich bestanden wurden, können wir ziemlich sicher sein, dass isEven() wie erwartet funktioniert.

Mal sehen, was passiert, wenn eine Behauptung fehlgeschlagen ist.

1
// Let's test this function

2
function isEven(val) {
3
	return val % 2 === 0;
4
}
5
6
test('isEven()', function() {
7
	ok(isEven(0), 'Zero is an even number');
8
	ok(isEven(2), 'So is two');
9
	ok(isEven(-4), 'So is negative four');
10
	ok(!isEven(1), 'One is not an even number');
11
	ok(!isEven(-7), 'Neither does negative seven');
12
13
	// Fails

14
	ok(isEven(3), 'Three is an even number');
15
})

Hier ist das Ergebnis:

a test contains failed assertion for isEven()a test contains failed assertion for isEven()a test contains failed assertion for isEven()

Die Behauptung ist fehlgeschlagen, weil wir sie absichtlich falsch geschrieben haben. Wenn der Test in Ihrem eigenen Projekt nicht erfolgreich ist und alle Behauptungen korrekt sind, wissen Sie, dass ein Fehler gefunden wurde.

Weitere Behauptungen

ok() ist nicht die einzige Behauptung, die QUnit liefert. Es gibt andere Arten von Behauptungen, die beim Testen Ihres Projekts hilfreich sind:

Vergleich Behauptung

Die Vergleichsaussage equals() erwartet, dass ihr erster Parameter (der der tatsächliche Wert ist) gleich ihrem zweiten Parameter (der der erwartete Wert ist) ist. Es ähnelt ok(), gibt jedoch sowohl tatsächliche als auch erwartete Werte aus, was das Debuggen erheblich vereinfacht. Wie bei ok() wird ein optionaler dritter Parameter als Nachricht angezeigt.

Also statt:

1
test('assertions', function() {
2
	ok( 1 == 1, 'one equals one');
3
})
a boolean assertiona boolean assertiona boolean assertion

Sie sollten schreiben:

1
test('assertions', function() {
2
	equals( 1, 1, 'one equals one');
3
})
a comparison assertiona comparison assertiona comparison assertion

Beachten Sie die letzte "1", bei der es sich um den Vergleichswert handelt.

Und wenn die Werte nicht gleich sind:

1
test('assertions', function() {
2
	equals( 2, 1, 'one equals one');
3
})
a failed comparison assertiona failed comparison assertiona failed comparison assertion

Es gibt viel mehr Informationen und erleichtert das Leben erheblich.

Die Vergleichszusicherung verwendet "==", um ihre Parameter zu vergleichen, sodass kein Array- oder Objektvergleich durchgeführt wird:

1
test('test', function() {
2
	equals( {}, {}, 'fails, these are different objects');
3
	equals( {a: 1}, {a: 1} , 'fails');
4
	equals( [], [], 'fails, there are different arrays');
5
	equals( [1], [1], 'fails');
6
})

Um diese Art von Gleichheit zu testen, liefert QUnit eine andere Art von Behauptung: identische Behauptung.

Identische Behauptung

Die identische Behauptung same() erwartet dieselben Parameter wie equals(), aber es handelt sich um eine tief rekursive Vergleichsaussage, die nicht nur für primitive Typen, sondern auch für Arrays und Objekte funktioniert. Im vorherigen Beispiel werden alle Zusicherungen bestanden, wenn Sie sie in identische Zusicherungen ändern:

1
test('test', function() {
2
	same( {}, {}, 'passes, objects have the same content');
3
	same( {a: 1}, {a: 1} , 'passes');
4
	same( [], [], 'passes, arrays have the same content');
5
	same( [1], [1], 'passes');
6
})

Beachten Sie, dass same() nach Möglichkeit '===' verwendet, um Vergleiche durchzuführen. Dies ist daher nützlich, wenn Sie spezielle Werte vergleichen:

1
test('test', function() {
2
	equals( 0, false, 'true');
3
	same( 0, false, 'false');
4
	equals( null, undefined, 'true');
5
	same( null, undefined, 'false');
6
})

Strukturieren Sie Ihre Behauptungen

Alle Aussagen in einem einzigen Testfall zusammenzufassen, ist eine wirklich schlechte Idee, da es sehr schwer zu pflegen ist und kein sauberes Ergebnis liefert. Sie sollten sie strukturieren und in verschiedene Testfälle einteilen, die jeweils auf eine einzige Funktionalität abzielen.

Sie können Testfälle sogar in verschiedene Module organisieren, indem Sie die Modulfunktion aufrufen:

1
module('Module A');
2
test('a test', function() {});
3
test('an another test', function() {});
4
5
module('Module B');
6
test('a test', function() {});
7
test('an another test', function() {});
structure assertionsstructure assertionsstructure assertions

Asynchroner Test

In den vorherigen Beispielen werden alle Zusicherungen synchron aufgerufen, d.h. sie werden nacheinander ausgeführt. In der realen Welt gibt es auch viele asynchrone Funktionen, wie Ajax-Aufrufe oder Funktionen, die von setTimeout() und setInterval() aufgerufen werden. Wie können wir diese Art von Funktionen testen? QUnit bietet einen speziellen Testfall namens "asynchroner Test", der sich dem asynchronen Testen widmet:

Versuchen wir zunächst, es regelmäßig zu schreiben:

1
test('asynchronous test', function() {
2
	setTimeout(function() {
3
		ok(true);
4
	}, 100)
5
})
an incorrent example of asychronous testan incorrent example of asychronous testan incorrent example of asychronous test

Sehen Sie? Es ist, als hätten wir keine Behauptung geschrieben. Dies liegt daran, dass die Zusicherung asynchron ausgeführt wurde und der Testfall zum Zeitpunkt des Aufrufs bereits abgeschlossen war.

Hier ist die richtige Version:

1
test('asynchronous test', function() {
2
	// Pause the test first

3
	stop();
4
	
5
	setTimeout(function() {
6
		ok(true);
7
8
		// After the assertion has been called,

9
		// continue the test

10
		start();
11
	}, 100)
12
})
a correct example of asychronous testa correct example of asychronous testa correct example of asychronous test

Hier verwenden wir stop(), um den Testfall anzuhalten, und nachdem die Zusicherung aufgerufen wurde, verwenden wir start(), um fortzufahren.

Das Aufrufen von stop() unmittelbar nach dem Aufrufen von test() ist weit verbreitet. Daher bietet QUnit eine Verknüpfung: asyncTest(). Sie können das vorherige Beispiel folgendermaßen umschreiben:

1
asyncTest('asynchronous test', function() {
2
	// The test is automatically paused

3
	
4
	setTimeout(function() {
5
		ok(true);
6
7
		// After the assertion has been called,

8
		// continue the test

9
		start();
10
	}, 100)
11
})

Es gibt eine Sache, auf die Sie achten müssen: setTimeout() ruft immer seine Rückruffunktion auf, aber was ist, wenn es sich um eine benutzerdefinierte Funktion handelt (z. B. ein Ajax-Aufruf)? Wie können Sie sicher sein, dass die Rückruffunktion aufgerufen wird? Und wenn der Rückruf nicht aufgerufen wird, wird start() nicht aufgerufen und der gesamte Unit-Test bleibt hängen:

unit testing hangsunit testing hangsunit testing hangs

Also hier ist was Sie tun:

1
// A custom function

2
function ajax(successCallback) {
3
	$.ajax({
4
		url: 'server.php',
5
		success: successCallback
6
	});
7
}
8
9
test('asynchronous test', function() {
10
	// Pause the test, and fail it if start() isn't called after one second

11
	stop(1000);
12
	
13
	ajax(function() {
14
		// ...asynchronous assertions

15
16
		start();
17
	})
18
})

Sie übergeben eine Zeitüberschreitung an stop(), die QUnit mitteilt: "Wenn start() nach dieser Zeitüberschreitung nicht aufgerufen wird, sollten Sie diesen Test nicht bestehen." Sie können sicher sein, dass die gesamten Tests nicht hängen bleiben und Sie werden benachrichtigt, wenn etwas schief geht.

Wie wäre es mit mehreren asynchronen Funktionen? Wo setzen Sie den start()? Sie setzen es in setTimeout():

1
// A custom function

2
function ajax(successCallback) {
3
	$.ajax({
4
		url: 'server.php',
5
		success: successCallback
6
	});
7
}
8
9
test('asynchronous test', function() {
10
	// Pause the test

11
	stop();
12
	
13
	ajax(function() {
14
		// ...asynchronous assertions

15
	})
16
17
	ajax(function() {
18
		// ...asynchronous assertions

19
	})
20
21
	setTimeout(function() {
22
		start();
23
	}, 2000);
24
})

Das Zeitlimit sollte ausreichend lang sein, damit beide Rückrufe aufgerufen werden können, bevor der Test fortgesetzt wird. Aber was ist, wenn einer der Rückrufe nicht aufgerufen wird? Wie kannst du das wissen? Hier kommt expect() ins Spiel:

1
// A custom function

2
function ajax(successCallback) {
3
	$.ajax({
4
		url: 'server.php',
5
		success: successCallback
6
	});
7
}
8
9
test('asynchronous test', function() {
10
	// Pause the test

11
	stop();
12
13
	// Tell QUnit that you expect three assertions to run

14
	expect(3);
15
16
	ajax(function() {
17
		ok(true);
18
	})
19
20
	ajax(function() {
21
		ok(true);
22
		ok(true);
23
	})
24
25
	setTimeout(function() {
26
		start();
27
	}, 2000);
28
})

Sie übergeben eine Zahl zu expect(), um QUnit mitzuteilen, dass X viele Zusicherungen ausgeführt werden sollen. Wenn eine der Zusicherungen nicht aufgerufen wird, stimmt die Zahl nicht überein, und Sie werden benachrichtigt, dass ein Fehler aufgetreten ist.

Es gibt auch eine Verknüpfung für expect(): Sie übergeben einfach die Zahl als zweiten Parameter an test() oder asyncTest():

1
// A custom function

2
function ajax(successCallback) {
3
	$.ajax({
4
		url: 'server.php',
5
		success: successCallback
6
	});
7
}
8
9
// Tell QUnit that you expect three assertion to run

10
test('asynchronous test', 3, function() {
11
	// Pause the test

12
	stop();
13
14
	ajax(function() {
15
		ok(true);
16
	})
17
18
	ajax(function() {
19
		ok(true);
20
		ok(true);
21
	})
22
23
	setTimeout(function() {
24
		start();
25
	}, 2000);
26
})

Abschluss

Das ist alles, was Sie wissen müssen, um mit QUnit loszulegen. Unit-Tests sind eine großartige Methode, um Ihren Code vor dem Veröffentlichen zu testen. Wenn Sie noch keine Unit-Tests geschrieben haben, ist es Zeit, loszulegen! Danke fürs Lesen!

  • Folgen Sie uns auf Twitter oder abonnieren Sie den Nettuts+ RSS-Feed, um die besten Webentwicklungs-Tutorials im Web zu erhalten.