DevOps – Testautomation II – Webapplikationen & Mobile Device Clouds
Viele Unternehmen haben ihren Softwareentwicklungs-Prozess agil ausgerichtet, um Releases schneller und hochwertiger produktiv zu setzen, sprich, die "Time to Market" zu reduzieren. Denn noch immer gilt die Regel: nur Software, die vom Kunden genutzt werden kann, bringt dem Unternehmen Geld. Um dieses Vorhaben zu unterstützen, setzen immer mehr Unternehmen auf Continuous Delivery und DevOps.
Dieser Beitrag befasst sich mit der Notwendigkeit einer durchgehenden Testautomation in einer Continuous Delivery-Pipeline (Abb. 1) um erfolgreiche kontinuierliche Auslieferung in das Produktionssystem zu gewährleisten. Es wird erklärt welche Werkzeuge, Frameworks und technische Ansätze möglich sind, um in vollem Umfang von automatisierten Tests profitieren zu können.
Testautomation für Applikations Code (Feature Code)
Die Stages innerhalb einer Continuous Delivery-Pipeline haben verschiedene Testziele. Nur wenn ein Build alle Stages fehlerfrei durchlaufen hat wird der Build für die Produktion freigegeben. In der Acceptance Stage wird geprüft, ob die Anwendung und die dazugehörigen Services laufen. Es werden automatisierte Akzeptanztests ausgeführt, welche alle notwendigen Geschäftsfälle auf Regression prüfen. Die Capacity-Stage prüft, ob die Anwendung den erwarteten nichtfunktionalen Anforderungen (Performance, Load, Stress, …) Stand hält. Als letzten Überprüfungsschritt führt der Kunde in der User-Acceptance Stage einen manuellen Akzeptanztest durch.
Eine in unserem Beispiel besondere – aber nicht immer übliche – Rolle spielt dabei die Browser-Stage.
Cross-Browser-Problematik
E-Commerce-Applikationen stehen vor der großen Herausforderung, in vielen verschiedenen Browsern fehlerfrei laufen zu müssen. In der Browser-Stage wird überprüft, ob die Applikation in den verschiedenen Webbrowsern fehlerfrei funktioniert. Da der Betrieb eines eigenen Browser-Labs sehr aufwändig und kostspielig ist, greifen immer mehr Unternehmen auf Browser-Labs wie TestChameleon [1], Sauce Labs [2] oder BrowserStack [3] zu. Im folgenden Beispiel wird gezeigt, wie die Testautomation in der Browser-Stage mit Spock [4], Gebish [5], Webdriver [6] und Sauce Labs umgesetzt wird.
Testautomation mit Spock
Spock ist ein BDD-Framework (BDD [7]), das die Lücke zwischen Softwareentwicklern, Testern und Product Ownern schließt. Die Tests werden in natürlicher Sprache im Give-When-Then-Format beschrieben. Testfälle werden in Spezifikationsdateien definiert und decken ein oder mehrere Testszenarien ab. Spock interpretiert die Tests in Verbindung mit Gebish und WebDriver, um die Testfälle im Browser zu steuern.
Gebish ist eine Abstraktion des WebDriver API und verbindet die ausdrucksvolle und prägnante Sprache von Groovy mit einer jQuery-ähnlichen Lokalisierung der HTML-Elemente. Hinzu kommt, dass PageObjects (s. Pageobject-Pattern) zur Abstraktion der HTML-Seiten ermöglicht werden.
Testen der Login-Funktion
Für die Login-Funktion einer E-Commerce-Website (s. Abb.2) gilt es, nun einen Testfall (Listing 1) für ein gültiges Login zu erstellen. Der Benutzer meldet sich an der Website mit einem gültigen User an und wird nach erfolgreicher Anmeldung auf die Dashboard-Seite weitergeleitet.
Listing 1: Testfall "Gültiges Login"
package specs.common import pages.common.LoginPage import pages.common.DashboardPage import spock.lang.Stepwise @Stepwise class Login_CheckForValidAndInvalidUsers extends TestBaseSpec { def SetupSpec { -- some stuff to launch LoginPage } def "Login with valid User"() { given: at LoginPage when: performLogin(’Rudolf’,’TestXXX’) then: at DashboardPage } }
PageObject – Pattern
SDOK[8] beschreibt PageObjects wie folgt
"…PageObjects - Within your web app's UI there are areas that your tests interact with. A PageObject simply models these as objects within the test code. This reduces the amount of duplicated code and means that if the UI changes, the fix need only be applied in one place.
PageObjects can be thought of as facing in two directions simultaneously. Facing towards the developer of a test, they represent the services offered by a particular page. Facing away from the developer, they should be the only thing that has a deep knowledge of the structure of the HTML of a page (or part of a page).
It's simplest to think of the methods on a PageObject as offering the "services" that a page offers rather than exposing the details and mechanics of the page. As an example, think of the inbox of any web-based email system.
Amongst the services that it offers are typically the ability to compose a new email, to choose to read a single email, and to list the subject lines of the emails in the inbox. How these are implemented shouldn't matter to the test …"
PageObject der Login-Seite
Listing 2 zeigt das PageObject der Login-Seite als eigene Klasse. Zu beachten ist, dass das DSL-Keyword "performLogin" in der PageObject-Klasse implementiert ist.
Ebenfalls wird eine "at checker"-Funktion implementiert, mit der überprüft wird, ob der Browser die richtige HMTL-Seite geladen hat.
Listing 2: PageObject der Login-Seite
package pages.common import geb.* import pages.common.LayoutBasePage class LoginPage extends LayoutBasePage { static at = { title=='E-Commerce Login' } static content = { form { find('#loginform') } username { form.find('username')} password { from.find('password')} loginButton {('input', value:'Login')} errMsg { $('div', id:'errorbox')} } def performLogin (un, pw) { form.username = un form.password = pw loginButton.click() } }
Das Beispiel "Gültiges Login" aus Listing 1 kann jetzt um nicht gültige Login-Versuche erweitert werden (Listing 3). Hier kommt in der Where-Klausel der DataDriven-Ansatz ins Spiel.
Listing 3: Testszenario
package specs.common import pages.common.LoginPage import pages.common.DashboardPage import spock.lang.Stepwise @Stepwise class Login_CheckForValidAndInvalidUsers extends TestBaseSpec { def SetupSpec { -- some stuff to launch LoginPage } def "Login with valid User"() { given: at LoginPage when: performLogin(’Rudolf’,’TestXXX’) then: at DashboardPage } -- some stuff to launch LoginPage def "Login With Invalid User"() { given: at LoginPage when: performLogin(un,pw) then: waitFor { errMsg.text() == err} where: un | pw | err "hans" | "1111" | "Wrong username or password!" "hugo" | "2222" | "User hugo is not active!" } }
Verbindung zum Browser
Es besteht nun die Möglichkeit, den Test auf der lokalen Maschine, in einem internen/externen Desktop Browser Lab oder in einem Mobile Browser Lab in der Cloud (s. Infobox) durchzuführen. Nachfolgend werden wir alle drei Möglichkeiten stark vereinfacht und reduziert auf das Wesentliche erklären. Nachdem wir die Tests definiert haben, muss als nächstes die Verbindung zu einem Browser hergestellt werden.
driver = new FirefoxDriver()
Auf der lokalen Maschine soll der dort installierte Firefox-Browser verwendet werden, im internen Desktop Browser Lab der Internet Explorer 10 unter Windows 8. Eine Besonderheit stellt das Mobile Browser Lab in der Cloud dar. Dieses wird verwendet, um mobile Browser zu testen. Im Beispiel verwenden wir die aktuelle Safari-Version auf einem iPad 10.6 bei Sauce Labs. Welcher Browser zur Ausführung verwendet wird, ist in der Konfigurationsdatei
GebConfig.groovy
des Frameworks definiert (Listing 4).
Listing 4: Auszug aus GebConfig.groovy
def env = System.getProperty("environment","") if (env == "SAUCE") { DesiredCapabilities capabillities = DesiredCapabilities.ipad(); capabillities.setCapability("platform", "OS X 10.6"); capabillities.setCapability("build", "iPad/10.6/5.0"); driver = { new RemoteWebDriver(new URL("http://ecomm:ee20a88a-918a@ondemand.saucelabs.com:80/wd/hub"),capabillities); } else if (env == "INTERNAL") DesiredCapabilities capabillities = DesiredCapabilities.internetExplorer() capabillities.setCapability["platform"] = "Windows 8" capabillities.setCapability("build", "Win8/IE10"); driver = { new RemoteWebDriver(new URL("http://testIntern:80/wd/hub"),capabillities); } else driver = { new FirefoxDriver() } }
Mobile Browser Labs in der Cloud
Anbieter von mobilen Webseiten müssen ihre Anwendungen ausgiebig testen, um sicherzustellen, dass diese auf den wichtigsten Browsern und Plattformen fehlerfrei laufen. Trotz des Drucks der immer kürzer werdenden Entwicklungszyklen, ist das Testen für alle Betriebssysteme und Geräteplattformen eine notwendige und schwierige Aufgabe. Um langfristigen Erfolg in einem stark fragmentiert und wettbewerbsorientierten globalen Markt zu gewährleisten, sind in der Zwischenzeit die Testaufwände größer als die der eigentlichen Entwicklung. Denn kein Unternehmen kann sich ein schlechtes Rating in den App-Stores leisten.
Browser Labs in der Cloud ermöglichen Entwicklungs- und Testteams auf echte oder virtuelle mobile Endgeräte incl. verschiedenster Browser zuzugreifen. Diese einfach zu bedienende Lösung beschleunigt die Anwendungsbereitstellung [9].
Ausführung in der Pipeline
Nun muss der Test in der Continuous Delivery-Pipeline orchestriert werden. In unserem Beispiel verwenden wir Jenkins [10] für die Steuerung der Pipeline. Die Pipeline selbst wird über einen Parent-Job (Abb.3) implementiert, in dem die einzelnen Stages als Downstream-Jobs definiert und angestoßen werden.
In unserem Beispiel beschäftigen wir uns nur mit dem Downstream-Job für die Browser-Stage.
Für jeden Browser-Test gibt es einen eigenen Build-Step (Abb. 4), der über einen Parameter gesteuert wird. Als Parameter wird das zu verwendende Browser-Environment an den Job übergeben (Abb. 5).
Jenkins meets GebConfig.groovy
Wie bereits beschrieben, wird der Test über die Konfigurationsdatei
GebConfig.groovy
gesteuert.
Nun stellt sich die Frage, wie diese Konfigurations-Datei und Jenkins miteinander kommunizieren, um zu wissen welche Browser-Umgebung verwendet werden soll.
Ein nützliches Feature von Jenkins ermöglicht es, dass Job- und Build-Parameter während der Durchführung als System-Properties zur Verfügung gestellt werden. Diese werden nun zur Laufzeit in die Konfigurationsdatei eingelesen.
def env = System.getProperty ("environment","")
Testergebnisse der Pipeline
Nun gilt unser Augenmerk der Durchführung. Nachdem auf Grund eines Commits die Pipeline durchgeführt wurde, ist es nun an der Zeit, die Ergebnisse der Pipeline zu analysieren.
Das Jenkins-Log des Upstream-Jobs (Abb.6) zeigt an, dass der letzte Build die Pipeline nicht fehlerfrei durchlaufen hat.
Die Analysen der Logs ergaben, dass die Tests in der Mobile Device Cloud fehlerhaft waren.
Error Message geb.waiting.TimeoutException: element not found in 80.0 seconds
Fehleranalyse in der Mobile Browser Cloud
Saucelabs bietet die Möglichkeit, Videos, Screenshots und Logs der Testdurchführung zu analysieren. So ist es ein Leichtes, den Grund für den fehlgeschlagenen Test zu finden. In unserem Fall zeigt der Screenshot des Videos (Abb.7), dass der Login-Button im mobilen IOS-Browser nicht richtig dargestellt wird. Die Fehlerbehebung ist jetzt nur eine Sache von Minuten und nach dem Commit der Änderung bekommt die Pipeline eine neue Chance, sich zu beweisen.
Warum die Login-Maske am mobile Browser auf einem iPad ohne Login-Button angezeigt wird, ist eine Sache, die jetzt geklärt werden muss. Die automatisierten Tests haben jedenfalls ihren Beitrag dazu geleistet, dass die fehlerhafte Software es nicht bis in die Produktion geschafft hat. Somit hat die Pipeline ihre Aufgabe erfüllt.
Fazit
Zur schnelleren und qualitativ hochwertigeren Auslieferung ihrer Releases setzen Unternehmen heute auf DevOps und Continuous Delivery. Continuous Delivery ohne automatisierte Tests ist aber nur die Hälfte wert. Testautomation ist eine oft unterschätzte Disziplin und wird stiefmütterlich behandelt. Um die enge Zusammenarbeit von Produktmanagement, Operations, Developement und Testern zu unterstützen, sollten BDD-Frameworks zum Einsatz kommen, die es ermöglichen, Tests in natürlicher Sprache zu spezifizieren. Neben dem Applikationscode muss Testautomation allerdings auch den Code der Applikationsumgebung abdecken.
- TestChameleon
- Sauce Labs
- BrowserStack
- Google Code: Spock
- Gebish
- WebDriver
- Wikipedia: BDD - Behavior Driven Development
- Github: PageObjects
- Browser-Labs:Testobject, Saucelabs,Device Anywhere, Xamarin,
- Jenkins