Anzeige
Anzeige
Artikel
Artikel merken

Grünes Licht für korrekten Code: Testgetriebene Entwicklung mit JavaScript

Durch den Siegeszug des Webs ist JavaScript heute eine der wichtigsten Programmiersprachen, die bei einer Vielzahl von Websites und Diensten zum Einsatz kommt. Um so wichtiger ist es, dass die Code-Basis korrekt ist und möglichst wenig Fehler enthält. Testgetriebene JavaScript-Entwicklung könnte der Schlüssel zum Erfolg sein. Wir zeigen, was dahinter steckt.

8 Min. Lesezeit
Anzeige
Anzeige

(Foto: birdys / Photocase)

Ihren Anfang nahm die testgetriebene Entwicklung in den 1960er Jahren im Mercury-Programm der NASA. Seitdem wurde diese Art der Software-Entwicklung in den verschiedensten Programmiersprachen adaptiert, bis sie schließlich auch in der Webentwicklung Einzug hielt. Nachdem die Client-Geräte stetig an Rechenleistung gewannen, verlagerte sich auch der Anteil der Applikationslogik in Richtung Client. Das hatte zur Folge, dass JavaScript als clientseitige Programmiersprache nicht nur für simple Dinge wie Formularvalidierung oder das Ein- und Ausblenden von HTML-Elementen verwendet wurde, sondern auch für vollwertige Applikationen, die ein höheres Maß an Qualität verlangen. Ein möglicher Ansatz dafür ist die testgetriebene Entwicklung.

Die Qualität steigern

Anzeige
Anzeige

Denn wenn JavaScript-Applikationen zunehmend businesskritisch werden, also auch in Bereichen zum Einsatz kommen, in denen ein finanzielles Risiko für ein Unternehmen besteht, kann die testgetriebene JavaScript-Entwicklung als eine besondere Form der Qualitätssicherung zum Einsatz kommen. Dadurch steigt der Anspruch an Qualität und die Methoden der Entwicklung und die eingesetzten Werkzeuge müssen entsprechend angepasst werden. Die testgetriebene Entwicklung kann also ein wichtiger Baustein sein, um diesen erhöhten Qualitätsanspruch an die Software zu erfüllen.

Ein weiterer, nicht sofort ersichtlicher Grund für testgetriebene Entwicklung ist, dass man sich dadurch mehr mit dem zu lösenden Problem auseinandersetzt: Die Anforderungen werden zunächst in einer Reihe von Tests beschrieben und dann erst durch die eigentliche Implementierung gelöst.

Anzeige
Anzeige

Von Rot nach Grün

„Test first“ – mit diesen zwei Worten lässt sich die testgetriebene Entwicklung ganz einfach beschreiben. Wird strikt testgetrieben entwickelt, gibt es keine Zeile Quellcode, für die nicht zuvor ein Test geschrieben wurde. Ganz so konsequent lässt sich diese Art der Entwicklung in der Realität aber meist nicht umsetzen, da pro Funktion nicht nur ein Test anfällt, sondern mindestens zwei: ein Positiv-Test und einer für den Fehlerfall. Bei der testgetriebenen Entwicklung bewegt man sich in einem Muster, das Red-Green-Refactor genannt wird.

Anzeige
Anzeige
  • Red: Zunächst formuliert man einen Test. Zu diesem Zeitpunkt gibt es noch keine Implementierung. Bekannt ist lediglich die Anforderung an die zu entwickelnde Funktion. Der Test schlägt initial fehl.
  • Green: Der nächste Schritt hat das Ziel, den Test erfolgreich ablaufen zu lassen. Idealerweise implementiert man nur das, was unbedingt für den Erfolg des Tests erforderlich ist.
  • Refactor: Dieser Schritt dient zum Aufräumen. Duplikate im Code werden entfernt und nicht optimal implementierte Stellen mit laufenden Tests verbessert.

Dieser Zyklus wird quasi als Endlosschleife während der Entwicklung wiederholt.

Erste Schritte

Bevor die testgetriebene Entwicklung in JavaScript beginnen kann, muss zunächst die Umgebung dafür vorbereitet werden. Im Lauf der Zeit sind für JavaScript zahlreiche Testframeworks entstanden, die zum Einsatz kommen können, um testgetrieben Applikationen zu entwickeln. Weit verbreitet sind beispielsweise Jasmine oder QUnit. Eingebunden in eine HTML-Seite stellen sie Funktionen zur Verfügung, mit denen sich Tests formulieren lassen. Der Nachteil hierbei ist, dass die Tests direkt im Browser laufen müssen, was den Wechsel von der Entwicklungsumgebung in den Browser und ein Neuladen der Seite erforderlich macht. Zur Lösung dieses Problems bietet sich die Verwendung von zusätzlichen Infrastruktur-Frameworks wie beispielsweise Karma an. Die von ihnen bereitgestellte Serverkomponente erlaubt die Registrierung von verschiedenen Browsern für das anschließende Testen.

Anzeige
Anzeige

Zwischen dem Setup und dem ersten wirklichen Test liegt allerdings noch einiges an Arbeit. Zunächst muss das bereits erwähnte Problemverständnis geschaffen werden. Dafür ist es meist erforderlich, dass die Entwickler mit den Auftraggebern sprechen und eine Beschreibung des zu implementierenden Sachverhalts erhalten. Ist das Problem verstanden und existiert ein grobes Konzept zum Vorgehen, kann mit dem Einstieg in den Red-Green-Refactor-Zyklus begonnen und der erste Test erstellt werden.

Mit der klaren, nachvollziehbaren Syntax des Testframeworks Jasmine lassen sich einfache JavaScript-Tests formulieren. (Screenshot: jasmine.github.io)

Mit der klaren, nachvollziehbaren Syntax des Testframeworks Jasmine lassen sich einfache JavaScript-Tests formulieren. (Screenshot: jasmine.github.io)

Der folgende Abschnitt erläutert die testgetriebene Entwicklung anhand eines sehr einfachen Beispiels. Hierfür soll eine Funktion entstehen, die zwei Werte addiert und das Resultat zurückgibt.

Red

Der erste Schritt zu einem guten Test besteht darin, ein Problem zu formulieren, das dem Entwickler mehr Informationen über das zu testende System liefert. Im Falle des Beispiels ist dies der Aufruf der Funktion mit zwei Werten und die Prüfung auf einen bestimmten Rückgabewert. Dieser Test stellt sicher, dass die Funktion existiert und dass durch den Aufruf eine bestimmte Ausgabe erzeugt wird.

Anzeige
Anzeige

Je nach verwendetem Framework kann auf verschiedene Formen der Organisation von Tests zurückgegriffen werden. Für dieses Beispiel kommt Jasmine als Testframework zum Einsatz. Durch den Aufruf der Methode describe wird eine Gruppe von Tests erstellt, die nach Möglichkeit verschiedene Aspekte des gleichen Sachverhalts überprüfen. describe-Aufrufe lassen sich auch ineinander verschachteln, um komplexere Strukturen aufzubauen. Die eigentlichen Tests werden durch Aufrufe der it-Funktion erstellt. Wichtig bei der Formulierung von Tests ist immer, diese aussagekräftig zu benennen.

Red – ein Test mit Jasmine

describe('Calculator', function() {
	it('should add 1 and 1 and return 2', function() {
		var calculator = new Calculator();
		var result = calculator.add(1, 1);
		expect(result).toBe(2);
	});
});

Listing 1

Ein Test sollte möglichst nach dem Triple-A-Muster aufgebaut sein. Dies gewährleistet, dass sämtliche Tests eine ähnliche Struktur aufweisen, was für eine bessere Lesbarkeit sorgt. Das erste A steht für Arrange. Im Beispiel wird an dieser Stelle die Umgebung vorbereitet und eine Instanz erzeugt. Das zweite A bedeutet Act, also die Ausführung des zu testenden Sachverhalts. Im dritten A, dem Assert, wird schließlich überprüft, ob das Ergebnis des zweiten Schritts mit der Erwartung übereinstimmt. Nachdem hier testgetrieben vorgegangen wird, schlägt der Test erwartungsgemäß fehl.

Green

Das Ziel im nächsten Schritt ist es, einen erfolgreichen Testlauf zu erreichen. Der Fokus liegt dabei nicht auf einer perfekten Implementierung, sondern auf einer möglichst einfachen. Dieses Vorgehen soll sicherstellen, dass sich der Entwickler auf das eigentliche Problem konzentriert. Ein einfaches Hilfsmittel ist es, sich an den Fehlermeldungen des Testlaufs abzuarbeiten. Im Falle des Beispiels bedeutet das, dass im ersten Schritt der Konstruktor Calculator erstellt werden muss, dann dem Prototyp die Funktion add hinzugefügt wird und diese schließlich den Wert 2 zurückgibt. Am Ende dieser drei Schritte steht der erfolgreiche Testlauf.

Anzeige
Anzeige

Green – Implementierung der zu testenden Funktion

function Calculator() {}

Calculator.prototype.add = function() {
	return 2;
};

Listing 2

Arbeitet man testgetrieben, werden immer wieder Muster angewandt, die so genannten Test-Patterns. Das sind Vorgehensweisen, die sich in der Vergangenheit bei verschiedenen Problemstellungen als erfolgreich herausgestellt haben. Eines dieser Muster trägt den Namen Baby Steps und bezeichnet möglichst kleine Schritte bei der Entwicklung. Sie stellen sicher, dass zwischen zwei Tests keine unkalkulierbaren Risiken liegen, die eine umfangreiche Fehlersuche erforderlich machen oder Raum für unentdeckte Fehler öffnen.

Im Gegensatz dazu steht das Muster der Obvious Implementation. Dieses besagt, dass für offensichtliche Implementierungen, wie es beispielsweise die Addition zweier Werte ist, kein Test erforderlich ist. Wann dieses Muster greift, muss der Entwickler individuell oder zusammen mit seinem Team entscheiden.

Refactor

Laufen alle Tests erfolgreich ab, ist es an der Zeit, den Quellcode zu verbessern. Einerseits durch die Reduktion von Duplikaten im Code, aber auch durch Einführen dynamischer Komponenten. Wichtig dabei ist zum einen, dass durch das Refactoring keine Tests fehlschlagen dürfen, und zum anderen, dass die Änderungen durch die bestehenden Tests abgedeckt sind und keine neue Funktionalität eingeführt wird. Für das Beispiel könnte dies bedeuten, einen berechneten Wert zurückzugeben oder die Generierung der Instanz im Test in eine beforeEach-Funktion auszulagern. So eine Funktion wird vor jedem Test der Gruppe ausgeführt:

Anzeige
Anzeige

Refactor – Umbau des Tests und der Implementierung

describe('Calculator', function() {
	var calculator;
	beforeEach(function() {
		calculator = new Calculator();
	});
	it('should add 1 and 1 and return 2', function() {
		var result = calculator.add(1, 1);
		expect(result).toBe(2);
	});
});

function Calculator() {}

Calculator.prototype.add = function(a, b) {
	return a + b;
};

Listing 3

TDD jenseits von Red-Green-Refactor

Wer schon einmal mit JavaScript entwickelt hat, weiß, dass eine Applikation leider nicht nur aus Funktionen besteht, die eine definierte Ein- und Ausgabe haben. Stattdessen muss man mit Callback-Funktionen und Asynchronität umgehen, Exceptions behandeln und auf den Ablauf einer Zeitspanne warten. All diese Fälle machen das Schreiben von Tests schwierig und damit eine testgetriebene Entwicklung zumindest umständlicher. Glücklicherweise gibt es für diese und zahlreiche weitere Problemstellungen bereits etablierte Lösungen, auf die man zurückgreifen kann.

Bei Callback-Funktionen – das sind Funktionsobjekte, die zu einem späteren Zeitpunkt, meist nach Beendigung einer bestimmten Aktion, aufgerufen werden – können so genannte Spy-Objekte die Verwendung aufzeichnen. Ein Spy ist nur einer von mehreren Test Doubles. Daneben gibt es noch Stubs, die wie reguläre Funktionen arbeiten, deren Verhalten sich allerdings programmieren lässt und die so eine stabile Umgebung für Tests schaffen. Mocks schließlich erlauben, eine bestimmte Verwendung zu prüfen.

Zum Umgang mit Asynchronität, also der zeitlich versetzten Ausführung von Funktionen, liefern die meisten Frameworks bereits Behandlungsroutinen mit. Ähnliches gilt für den Umgang mit Exceptions, die somit nicht zum Abbruch des Tests führen.

Anzeige
Anzeige

Ein ganz wichtiger Bereich beim Testen sind Abhängigkeiten. Dies gilt sowohl für zeitliche Abhängigkeit beim Ablauf von Timeouts und Intervallen als auch für die Kommunikation mit einem Server oder für die Abhängigkeit von einer HTML-Struktur. Frameworks wie Sinon.js bieten die Möglichkeit, die Browserzeit anzuhalten und vollständig zu kontrollieren. Auch Antworten eines Servers lassen sich damit ohne die erforderliche Serverinfrastruktur testen, indem die Antwort vorprogrammiert wird und die Anfrage niemals den Browser in Richtung Server verlässt. Für die HTML-Abhängigkeiten gibt es die so genannten Fixtures – das sind vorbereitete HTML-Strukturen, die für den Test geladen werden und eine definierte Umgebung darstellen.

Umgang mit Abhängigkeiten zum Server

it('should fetch the data and build the list with 5 items', function() {
	var server = sinon.fakeServer.create();

	server.respondWith("GET", "/url/on/server",
		[200, { "Content-Type": "application/json" },
			'{"data":["a","b","c","d","e"]}']);

	$('body').append('<ul id="itemList"></ul>');
	fetchAndInsert();

	server.respond();

	expect($('#itemList li').length).toEqual(5);
});

Listing 4

Mit diesen Hilfsmitteln lassen sich die verschiedensten Facetten einer JavaScript-Applikation testgetrieben entwickeln und sie tragen so zu einer stabileren Software bei.

Ausblick

Auch die Entwickler verschiedener Frameworks haben die Vorteile von Tests erkannt und achten bei der Konzeption und Programmierung der Frameworks darauf, dass die Software Tests ermöglicht und damit eine testgetriebene Entwicklung von Applikationen unterstützt. Ein populäres Beispiel hierfür ist AngularJS. Dieses Framework wurde mit dem Ziel entwickelt, eine gute Testbarkeit für alle Komponenten zu ermöglichen.

Anzeige
Anzeige

Sind die Tests dann erst einmal geschrieben, lassen sie sich auch problemlos in einer Continuous-Integration-Umgebung einsetzen, wo sie regelmäßig und automatisiert ausgeführt werden, was weiter zu einer stabilen und qualitativ hochwertigen Software beiträgt.

Mehr zu diesem Thema
Fast fertig!

Bitte klicke auf den Link in der Bestätigungsmail, um deine Anmeldung abzuschließen.

Du willst noch weitere Infos zum Newsletter? Jetzt mehr erfahren

Anzeige
Anzeige
Schreib den ersten Kommentar!
Bitte beachte unsere Community-Richtlinien

Wir freuen uns über kontroverse Diskussionen, die gerne auch mal hitzig geführt werden dürfen. Beleidigende, grob anstößige, rassistische und strafrechtlich relevante Äußerungen und Beiträge tolerieren wir nicht. Bitte achte darauf, dass du keine Texte veröffentlichst, für die du keine ausdrückliche Erlaubnis des Urhebers hast. Ebenfalls nicht erlaubt ist der Missbrauch der Webangebote unter t3n.de als Werbeplattform. Die Nennung von Produktnamen, Herstellern, Dienstleistern und Websites ist nur dann zulässig, wenn damit nicht vorrangig der Zweck der Werbung verfolgt wird. Wir behalten uns vor, Beiträge, die diese Regeln verletzen, zu löschen und Accounts zeitweilig oder auf Dauer zu sperren.

Trotz all dieser notwendigen Regeln: Diskutiere kontrovers, sage anderen deine Meinung, trage mit weiterführenden Informationen zum Wissensaustausch bei, aber bleibe dabei fair und respektiere die Meinung anderer. Wir wünschen Dir viel Spaß mit den Webangeboten von t3n und freuen uns auf spannende Beiträge.

Dein t3n-Team

Melde dich mit deinem t3n Account an oder fülle die unteren Felder aus.

Bitte schalte deinen Adblocker für t3n.de aus!
Hallo und herzlich willkommen bei t3n!

Bitte schalte deinen Adblocker für t3n.de aus, um diesen Artikel zu lesen.

Wir sind ein unabhängiger Publisher mit einem Team von mehr als 75 fantastischen Menschen, aber ohne riesigen Konzern im Rücken. Banner und ähnliche Werbemittel sind für unsere Finanzierung sehr wichtig.

Schon jetzt und im Namen der gesamten t3n-Crew: vielen Dank für deine Unterstützung! 🙌

Deine t3n-Crew

Anleitung zur Deaktivierung
Artikel merken

Bitte melde dich an, um diesen Artikel in deiner persönlichen Merkliste auf t3n zu speichern.

Jetzt registrieren und merken

Du hast schon einen t3n-Account? Hier anmelden

oder
Auf Mastodon teilen

Gib die URL deiner Mastodon-Instanz ein, um den Artikel zu teilen.

Anzeige
Anzeige