smart | Webentwicklung
Alles rund um HTML5, PHP, WordPress & Co.

TDD/BDD in JavaScript mit dem BDD-Framework Jasmine

10. April 2012
Stephan
BDD-Framework Jasmine

Jasmine ist ein Unit-Testing Framework bzw. genauer gesagt ein Framework für Behavior-Driven Development (BDD), dass dazu dient JavaScript-Code zu testen.

Dabei ist Jasmine an das bekannte Ruby BDD-Framework RSpec angelehnt und leicht zu erlernen. Analog zu RSpec, werden auch mit Jasmine sogenannte Specs (in TDD sind es Tests) geschrieben, um das zu erwartende Verhalten des eigentlichen Codes zu testen.

In diesem Artikel möchte ich euch eine kurze Einführung in das BDD-Framework Jasmine geben.

Für weitere Informationen und Details zu Jasmine verweise ich auf die dazugehörige Dokumentation.

Jasmine downloaden & Projektverzeichnis anlegen

Bevor wir beginnen können, laden wir uns erst mal die Standalone-Version von Jasmine. (Angemerkt sei, dass die Code-Auszüge in diesem Artikel auf der Jasmine-Version 1.1.0 basieren.) Anschließend entpacken wir die heruntergeladene ZIP-Datei und erstellen uns dann folgendes Projektverzeichnis:

Projektstruktur für BDD-Stack-Beispiel mit Jasmine

Wir brauchen aus der ZIP-Datei letztendlich nur den lib&-Ordner und die SpecRunner.html-Datei für unser Projekt.

Beispiel: Stack

Am Beispiel eines Stacks werden wir nun die grundlegenden Aspekte von Jasmine kennen lernen. Als erstes erstellen wir dazu im Verzeichnis app/ die Datei Stack.js und im Verzeichnis specs/app/ die Spec-Datei StackSpec.js.

Zu Beginn wollen wir spezifizieren, dass beim Erstellen eines neuen Stacks, dieser leer ist. Hierzu fügen wir Folgendes in die StackSpec.js-Datei:

describe('Stack', function()
{
	it('should be empty when newly created', function()
	{
		var stack = new Stack();
		expect(stack.isEmpty()).toBeTruthy();
	});
});

Durch den typischen BDD-Charakter von Jasmine, ist der Code denke ich selbsterklärend. Wir beschreiben einen Stack und erwarten von unserem Stack, dass dieser leer ist, nachdem wir ihn erstellt haben.

Als nächstes wollen wir diesen Spec mittels Jasmine testen. Dazu müssen wir vorher noch kurz die SpecRunner.html-Datei anpassen und unsere Dateien einbinden:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Jasmine Spec Runner</title>
<link rel="shortcut icon" type="image/png" href="lib/jasmine-1.1.0/jasmine_favicon.png">
<link rel="stylesheet" type="text/css" href="lib/jasmine-1.1.0/jasmine.css">

<!-- include lib files here... -->
<script type="text/javascript" src="lib/jasmine-1.1.0/jasmine.js"></script>
<script type="text/javascript" src="lib/jasmine-1.1.0/jasmine-html.js"></script>

<!-- include source files here... -->
<script type="text/javascript" src="../app/Stack.js"></script>

<!-- include spec files here... -->
<script type="text/javascript" src="app/StackSpec.js"></script>

<!-- der Rest der "SpecRunner.html"-Datei bleibt so wie er war -->

Jetzt rufen wir einfach unsere SpecRunner.html-Datei in einem Webbrowser auf und sollten einen fehlgeschlagenen Spec sehen:

BDD-Stack-Beispiel - Jasmine-Spec-Fehler

Damit unser Spec nun grün wird und keinen Fehler wirft, passen wir nun die Stack.js-Datei an:

function Stack()
{
	this.tmpStack = new Array();

	this.isEmpty = function()
	{
		return this.tmpStack.length === 0 ? true : false;
	}
}

Wir erstellen uns also eine Stack-Klasse und implementieren eine Methode, die überprüft, ob der aktuelle Stack leer ist oder nicht. Nach erneutem Aufruf von Jasmine, sollten unser Spec nun erfolgreich sein:

BDD-Stack-Beispiel - Jasmine-Spec-Erfolg

War doch gar nicht so schwer oder?

Push & Pop für unseren Stack implementieren

Nachdem wir nun einen Stack erstellen können und wissen, dass dieser leer ist, wollen wir jetzt natürlich auch die Möglichkeit haben, Elemente hinzuzufügen (Push) und zu entfernen (Pop).

Da ich hier jetzt die Umsetzung nur andeute und nicht alle Einzelheiten eines Stacks berücksichtigen werde, empfehle ich euch ein ausführliches Stack-Beispiel für nähere Details.

In unserer StackSpec.js-Datei spezifizieren wir jetzt, dass der Stack nicht mehr leer ist, nachdem ein Element hinzugefügt wurde. Weiterhin erwarten wir, dass wenn der Stack nur ein Element enthält, der Stack nach einem Pop wieder leer ist und das aus dem Stack entfernte Element dem vorher hinzugefügten entspricht.

describe('Stack', function()
{
	describe('when empty', function()
	{
		beforeEach(function()
		{
			this.stack = new Stack();
		});

		it('should be empty', function()
		{
			expect(this.stack.isEmpty()).toBeTruthy();
		});

		describe('#push', function()
		{
			it('should not be empty after pushing an "A"', function()
			{
				this.stack.push('A');
				expect(this.stack.isEmpty()).toBeFalsy();
			});
		});
	});

	describe('when not empty', function()
	{
		beforeEach(function()
		{
			this.stack = new Stack();
			this.stack.push('A');
		});

		it('should not be empty', function()
		{
			expect(this.stack.isEmpty()).toBeFalsy();
		});

		describe('when contains just 1 element', function()
		{
			describe('#pop', function()
			{
				it('should be empty', function()
				{
	                this.stack.pop();
					expect(this.stack.isEmpty()).toBeTruthy();
				});
			});

			describe('#push pop element check', function()
			{
				it('should pop an "A" after pushing an "A"', function()
				{
					element = this.stack.pop();
					expect(element).toBe('A');
				});
			});
		});
	});
});

Auch hier denke ich ist der Code wieder selbserkärend aufgrund der „sprechenden“ BDD-Syntax von Jasmine.

Für nähere Informationen zu den verschiedenen Matchern, Hooks (beforeEach() etc.) und weiteren Aspekten zu Jasmine verweise ich nochmals auf die Dokumentation.

Damit nun unsere Specs und unsere Erwartungen an unseren Stack erfüllt werden, könnte unsere Stack.js-Datei wie folgt angepasst werden:

function Stack()
{
	this.tmpStack = new Array();

	this.empty = true;

	this.isEmpty = function()
	{
		return this.empty ? true : false;
	}

	this.push = function(element)
	{
		this.empty = false;
		this.tmpStack.push(element);
	}

	this.pop = function()
	{
		element = this.tmpStack.pop();
		if(this.tmpStack.length === 0)
		{
			this.empty = true;
		}
		return element;
	}
}

Natürlich gibt es noch diverse Erweiterungemöglichkeiten für unseren Stack, wie z.B. die Implementierung einer top()-Methode, die immer das oberste Element zurückgibt oder, dass ein Fehler geworfen wird, wenn der Stack leer ist und trotzdem die pop()-Methode aufgerufen wird.

Fazit

Es gibt wahrscheinlich schöneres als JavaScript-Code zu testen, aber ich finde mit dem BDD-Framework Jasmine macht sogar das Spaß und geht leicht von der Hand.

Nebenbei sei erwähnt, dass Jasmine sich auch wunderbar zum Testen von backbone.js-Anwendungen eignet und auch in Verbindung mit CoffeeScript einfach anzuwenden ist. Diesbezüglich werde ich demnächst eventuell auch mal ein oder zwei Artikel schreiben.

Wie findet ihr Jasmine und die Syntax? Welches Framework bevorzugt ihr zum Untit-Testing in JavaScript?

Kommentare  
0 Kommentare vorhanden
0 Trackbacks/Pingbacks vorhanden
Du bist herzlich eingeladen auch ein Kommentar zu hinterlassen!
Kommentar schreiben

Vielen Dank für dein Kommentar!