Red, Green, Jasmine

Test-driven JavaScript mit Jasmine
Kommentare

Durch seine weite Verbreitung, seine immer weiter fortschreitende Standardisierung und damit die Reduzierung von Browserinkompatibilitäten ist JavaScript zu einem Kernbestandteil einer jeden Webapplikation geworden. Diese Rolle macht es notwendig, auch den JavaScript-Quellcode unter strengeren Gesichtspunkten zu betrachten, wenn es um Qualität und Zuverlässigkeit geht.

PHP Magazin

Der Artikel „Test-driven JavaScript mit Jasmine“ von Sebastian Springer ist erstmalig erschienen im PHP Magazin 5.2012

Mit SUnit hat Kent Beck den Grundstein für eine sprachübergreifende Erfolgsgeschichte gelegt. SUnit war das erste Framework der xUnit-Familie von Unit-Test-Frameworks. Es wurde in eine Vielzahl von Sprachen portiert, beispielsweise in Java oder auch PHP. Aber auch für JavaScript existieren mittlerweile verschiedene Implementierungen von Unit-Test-Frameworks dieser Art. Kent Beck hat bei der Erstellung von Unit Tests das Test-driven Development als Vorgehensweise beschrieben. Geht man bei der Entwicklung nach diesen Richtlinien vor, erhält man automatisch eine hohe Abdeckung der Software mit Unit Tests, da sie die Erstellung der Software begleiten und dokumentieren. Des Weiteren zwingt Test-driven Development die Entwickler dazu, sich von vornherein Gedanken über das Design der Software zu machen. Die zugrunde liegende Vorgehensweise wird durch das „Test-driven Development Mantra“ beschrieben: Red/Green/Refactor.

Red/Green/Refactor

Bei Test-driven Development wird, wie bei allen agilen Methoden, in Iterationen vorgegangen. Zu Beginn jeder Iteration steht die Entwicklung eines Unit Tests für eine bestimmte Funktionalität, die allerdings noch nicht im Code vorhanden ist. Ist dieser Test implementiert, werden alle vorhandenen Tests ausgeführt. Der zuletzt eingefügte Test schlägt fehl, da der zu testende Code noch nicht existiert. Das bedeutet, dass der Status der Tests „rot“ ist, also nicht erfolgreich.

Als Reaktion auf den fehlschlagenden Test wird nun Code erstellt, der dafür sorgt, dass der Test erfolgreich durchlaufen wird. Der Fokus liegt dabei darauf, dass lediglich die Anforderungen des Tests erfüllt werden und nicht irgendeine Funktionalität, die in Zukunft irgendwann benötigt werden könnte. Diese Anforderung kann beispielsweise schon durch die Rückgabe eines festen Werts erreicht werden. Nach der Implementierung der Funktionalität werden die Tests erneut ausgeführt. Der Status sollte diesmal „grün“, also erfolgreich sein.

Der dritte und letzte Schritt besteht aus dem Refactoring des Quellcodes. In diesem Schritt wird der bestehende Quellcode aufgeräumt. Diese Aktion sollte sich allerdings nicht nur auf den eben erstellten, sondern auch auf schon länger existierenden Code beziehen. Der letzte Schritt trägt wesentlich dazu bei, die Applikation wartbar und erweiterbar zu halten, indem neuer Code in den bestehenden sauber integriert und der bestehende Code immer wieder überarbeitet wird. Hier kann unter anderem duplizierter Code zusammengefasst oder an verschiedenen Stellen benötigte Funktionalität in separate Klassen ausgelagert werden.

Umfangreiches Refactoring an verschiedenen Stellen im Quellcode wird ermöglicht, da durch Test-driven Development sämtlicher Code durch Unit Tests abgesichert ist und so ein erfolgreicher Testdurchlauf sicherstellt, dass die vorhandene Funktionalität fehlerfrei ist.

Test-driven Development hat einen entscheidenden Vorteil gegenüber anderen Vorgehensweisen, bei denen die Tests erst nach dem eigentlichen Quellcode erstellt werden: Der Entwickler muss sich vor der Implementierung bereits Gedanken machen, wie sein Code testbar gemacht wird beziehungsweise der Code wird automatisch testbar, da der Test vor dem eigentlichen Code entwickelt wird. Gerade in JavaScript tut sich das Problem von nicht testbarem Code auf. Wird der Quellcode beispielsweise direkt in die HTML-Struktur der Webseite eingebaut, ist keine Testbarkeit gegeben. Außerdem muss bei größeren Projekten darauf geachtet werden, dass der JavaScript-Quellcode in einer Struktur abgelegt wird, die die Erweiterbarkeit und Wartbarkeit der Applikation unterstützt.

Bei Test-driven Development ist es wichtig, auf einige wesentliche Aspekte zu achten. In erster Linie sollen Tests nur einen definierten Sachverhalt abtesten und so unabhängig vom übrigen System sein. Dadurch können gleich zwei verschiedene Ziele erreicht werden: Zum einen konzentriert man sich bei der Erstellung des Tests auf eine einzelne überschaubare Problemstellungen und zum anderen erreicht man Unabhängigkeit. Das bedeutet, dass sich die Tests nicht gegenseitig beeinflussen, da kein Test auf einen anderen aufbaut. In der Praxis heißt das, dass beim Fehlschlagen eines Tests die übrigen trotzdem noch einwandfrei weiterfunktionieren können.

Bei klassischen Unit Tests testet man die Funktionalität einer Softwareeinheit, indem man die Funktion mit unterschiedlichen Parametern aufruft und die jeweiligen Rückgabewerte überprüft. Je nachdem, welche Wertemengen für die Eingabeparameter zugelassen sind, kann eine hundertprozentige Abdeckung der möglichen Werte unter Umständen nicht erreicht werden. Bei der Konzeption von Tests gilt es daher, möglichst viele repräsentative Testwerte auszuwählen. Es empfiehlt sich hierbei, neben gültigen Eingabewerten auch solche zu verwenden, die an den Grenzen des Wertebereichs oder außerhalb desselben liegen. Durch ein derartiges Vorgehen kann sichergestellt werden, dass mit fehlerhaften Eingaben korrekt umgegangen wird.

Bei der Entwicklung der Tests sollte außerdem darauf geachtet werden, dass nicht der Algorithmus selbst, sondern die Schnittstelle nach außen getestet wird. Indem man sich nicht auf die Interna einer Funktion versteift, wird es möglich, Designfehler aufzudecken und unnötige Komplexität zu reduzieren.

Ein weiteres entscheidendes Kriterium von Tests ist eine schnelle Rückmeldung des Testresultats an den Entwickler. Test-driven Development baut darauf, dass die vorhandenen Tests häufig ausgeführt werden und der Entwickler sehr schnell erfährt, ob der Durchlauf erfolgreich war.

Um Entwickler bei der Erstellung von Unit Tests zu unterstützen, existieren zahlreiche Frameworks. Diese stellen verschiedene, so genannte Assertions zur Verfügung, durch die verglichen werden kann, ob ein bestimmter Wert einer Erwartung entspricht und so die entwickelte Funktionalität getestet werden kann. Des Weiteren werden in den meisten Fällen Möglichkeiten zur Gruppierung von Tests geboten. Aber auch die Auflösung von Abhängigkeiten durch Test-Doubles zählt zum Funktionsumfang von Testing-Frameworks.


Themen der kommenden Seiten:

  • Jasmine
  • Allgemeine Vorgehensweise
  • Matchers
  • Test-Doubles
  • Asynchrone Tests
  • Ausführung im Browser
  • Integration in die IDE
  • Ausblick
Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -