Effizientes Testen von Angular-Webanwendungen mit Protractor (Teil 1)

Ein Artikel von Adrianus Kleemans, Full-Stack Software Engineer, Diso AG

Wie kann in der agilen Software-Entwicklung garantiert werden, dass Business-Anforderungen stetig überprüft und nachhaltig sichergestellt werden? Protractor bringt bei Angular-basierten Webanwendungen das nötige Rüstzeug mit, um bei jeder Release-Iteration sicherzustellen, dass umgesetzte User Stories auch aus Benutzersicht noch funktionieren und keine Regressionen auftreten. In diesem Artikel soll erörtert werden, wie ein so mächtiges End-to-End Testing-Tool sinnvoll und effizient eingesetzt werden kann.

Im ersten, aktuellen Teil meines Beitrags gehe ich auf Protractor als Framework und auf die Gefahren ein, die sich unter unterschiedlichen Umständen ergeben können. Im zweiten Teil lesen Sie, welche Chancen sich aus einer gezielten Nutzung ergeben und welche Vorteile dies für das Projekt mit sich bringt.

Einleitung

Protractor basiert auf Selenium, ein Framework, das eine Schnittstelle zum Browser bietet und ganz grundsätzlich das Testen von beliebigen Webanwendungen ermöglicht.

Es lässt sich damit auf Interface-Ebene herumnavigieren, URLs aufrufen, Links und Buttons klicken, und Tastatureingaben simulieren. Mit Selenium können diese Tests unter anderem mit Java oder JavaScript geschrieben werden, Protractor bietet dabei von sich aus TypeScript an, was bei der Entwicklung hilfreich ist.

Bild: Offizielles Logo. «Protractor», der Winkelmesser als
Symbolbildfür Messbarkeit und Präzision (
Quelle)

Intelligentes Warten

Protractor geht aber einen Schritt weiter und bietet dem User die Möglichkeit, dynamisch auf die Anwendung zu warten. Dank seiner engen Anlehnung an Angular 1.x/2+ muss nirgends die im Selenium berüchtigten «wait()»-Befehle eingefügt werden – sobald Angular das Signal gibt, bereit zu sein, geht der Test weiter. Dies hat nebst einer schnelleren Durchführung der Tests (es wird nicht unnötig gewartet) auch zur Folge, dass viele Tests stabiler laufen. Sollten gewisse Aktionen wie eine Anfrage ans Backend einmal länger dauern, dann wird einfach etwas länger gewartet statt dass der Test fehlschlägt.

Beispiele

End-to-End (E2E) Tests sind nicht für jedes Projekt gleich notwendig oder sinnvoll.  Wenn aber der Technologie-Stack schon auf einem Client-Server-Ansatz mit Angular beruht, bietet sich Protractor als Testing-Framework an. Doch was genau sollte in welcher Tiefe getestet werden?

Ich möchte nachfolgend auf einige Chancen und Gefahren eingehen, die aus meiner Sicht mit dem Einsatz von End-to-End Tests ergeben.

Gefahr #1: Nur End-to-End-Tests

Wer schon nicht sicherstellen kann, dass viele kleine Teile für sich korrekt funktionieren, sollte nicht den Versuch unternehmen, das gesamte Konstrukt zu testen. Das heisst folglich auch, dass End-to-End Tests der falsche Einstiegspunkt sind, falls in einem Projekt – in beliebiger Phase, sei dies nun zu Beginn oder bei schon lauffähiger Software in Entwicklung – auf einmal mehr in Testing investiert wird.

Sowohl der prominente Artikel von Martik Fowler wie auch der sehr lesenswerte Bericht vom Google Testing Blog bringen einige Aspekte auf den Punkt:

  • End-to-End Tests sollten die Spitze der Testing-Pyramide sein, nicht die Basis
  • End-to-End Tests haben nebst dem Vorteil, einen echten Benutzer zu simulieren, einige Nachteile, namentlich Stabilität, Geschwindigkeit und die Isolierung des Fehlers

Bild: Vorgeschlagene Testing-Pyramide (Quelle: Google Testing Blog)

Es macht mehr Sinn, zu Beginn eine stabile Grundlage aufzubauen und in Unit- und Integrationstests zu investieren. E2E-Tests können eine gute Ergänzung bieten, sollten aber nicht die ersten oder einzigen Tests in einem Projekt darstellen.

Gefahr #2: Den richtigen Abstraktionsgrad finden für robuste Tests

Nebst der Entscheidung, zu welchem Zeitpunkt und für welche Funktionalität man E2E-Tests schreibt, müssen auch die Tests selbst auf dem richtigen Abstraktionsgrad gehalten werden. Aufgrund ihrer Natur orientieren sich E2E-Tests immer an der verfügbaren Oberfläche; Wechselt diese, ist oft auch eine Änderung am Test notwendig.

Als Beispiel ein Test für eine Benutzerverwaltung: Dieser stellte sicher, dass neue Benutzer angelegt werden können. In einem Dialog wird der Administrator aufgefordert, einen neuen Benutzernamen einzugeben:

Bild: Screenshot aus dem Diso-Produkt LeanLogic QA

Der entsprechende Protractor-Test griff per kompliziertem Selektor auf den Speichern-Knopf zu:

let saveButton = element(by.css(‚.ll-buttonbar > div:nth-child(4) > button:nth-child(1)‘));
saveButton.click();

Offensichtlich ist diese Variante anfällig für Veränderungen – ein zusätzlicher Button oder schon nur die Umstellung der Reihenfolge würden diesen Test fehlschlagen lassen. Besser wäre die Vergabe einer ID, damit viel direkter darauf zugegriffen werden kann:

let saveButton = element(by.css(‚#save-button‘));

Sinnvolle und möglichst robuste Wege zu finden, um im UI zu navigieren, gehören zu den anspruchsvolleren Aufgaben beim Schreiben von E2E-Tests. Dieses einfache Beispiel mag auf den ersten Blick als etwas trivial erscheinen, es zeigt aber sehr schön auf, was der Test eigentlich erwartet: Implizit gibt dieser Test wider «Ich erwarte einen klickbaren Button als viertes Kind-Element einer Button-Bar», eine sehr umständliche Formulierung für den eigentlichen Zweck: «Ich erwarte einen Speichern-Knopf», der daraufhin gedrückt werden soll.

Es ist daher hilfreich, E2E-Tests simultan mit der Anwendung selbst hochzufahren, damit das Zusammenspiel von Tests und Anwendung sichergestellt werden kann. Es ist auch im Nachhinein noch möglich, E2E-Tests zu schreiben, aber aufwändiger, und oft sind Änderungen an der Anwendung selbst nötig.

Gefahr #3: Ersatz von Integrationstests

In einem aktuellen Projekt arbeiten wir an der Darstellung von Dokumenten und alle wichtigen Ressourcen (Stammdaten, Metadaten, die Dokumente selbst) erhalten wir von Webservices, also anderen Systemen. Leider wurde in zu vielen Fällen versucht, Integrationen mit End-to-End Tests abzudecken, was mich und die anderen aktuellen Entwickler im Team mit einer breiten Basis an vorhandenen, aufwändig zu wartenden E2E-Tests konfrontiert.

Oft dauert die Suche nach dem genauen Fehler lange, Tests für unser eigenes System schlagen fehl aufgrund nicht verfügbarer Umsysteme und wir wissen zunächst nicht, ob ein «Breaking Change» bei der Umsetzung eines Features stattgefunden hat oder ein Webservice nicht verfügbar ist.

Bild: Jenkins-Screenshot mit fehlgeschlagenen Tests

Die Feedback-Kette ist entsprechend lang und die Pflege zudem sehr teuer, oft wird ein Tag oder mehr Sprint-Kapazität pro Woche dafür aufgewendet, nur um die bestehenden E2E-Test am laufen zu erhalten. Wir arbeiten daran, bessere Integrationstests aufzubauen und diese Teile aus den End-to-End Tests zu isolieren, aber dies ist ein aufwändiger Prozess, der sich prinzipiell vermeiden liesse.

(Der zweite Teil dieses Artikels erscheint in der nächsten News@Diso Ausgabe und zeigt die Chancen auf, die sich aus einer gezielten Nutzung ergeben und welche Vorteile dies für das Projekt mit sich bringt.)

Diso AG – Der Schweizer Daten- und Cloud-Experte

Die Diso AG ist ein renommierter IT-Dienstleister und langjähriger Oracle-Vertriebspartner in der Schweiz mit Schwerpunkten in den Bereichen Datenbanken und Cloud-Lösungen. Diso bietet ihren Kunden beispielsweise die Oracle Plattform as a Service-Lösung und die dazugehörige Datenmigration an. Kunden profitieren des Weiteren vom Komplettlösungsangebot im Sinne von Planung, Integration, Support inklusive Betrieb und Überwachung von IT-Infrastrukturen und Datenbanksystemen.
Im Bereich Software-Engineering entwickelt Diso massgeschneiderte IT und Software-Lösungen für unternehmensspezifische Anwendungen, wann immer sinnvoll mit einem mobile-first Ansatz. Zudem ist Diso Spezialist wenn es um die Software-basierte Optimierung von Performance geht. Auf die Kompetenz des traditionsreichen IT-Dienstleisters und Mittelständlers vertrauen bereits namhafte Kunden aus den Schwerpunktbranchen Banken, Versicherungen Detailhandel und öffentliche Verwaltung.
Die Diso designt wandlungsfähige IT-Systeme, entwickelt massgeschneiderte Software und ermöglicht die performante Verwendung und Auswertung von Informationen.

Über den Autor:

Adrianus Kleemans arbeitet bei der Diso AG als Full-Stack Software Engineer in den Bereichen Java- und RESTful-Anwendungen, Angular und DevOps. Über mehrere Jahre hat er beim Bund, der Bank- und Versicherungsbranche Expertise über moderne Web-Anwendungen aufgebaut, die im Rahmen von Scrum massgeschneidert für einen anspruchsvollen Endkunden angefertigt werden. Nebst seiner aktuellen Projekttätigkeit hat er auch die Kursleitung für Angular-Workshops und -Knowhow wahrgenommen und begleitet Projekte bei der Angular/TypeScript-Migration.