JMeter für Lazy Tester – Proxy

jmeter-logoMittlerweile ist das Testen an jeder Ecke, man testet dies, man testet das, es gibt für jeden Einsatzzweck ein eigenes Tool und trotzdem machen alle das gleiche und zwar testen, testen und wieder testen. Manche Tools machen das Leben einfacher andere wiederum schwerer, sobald die Menge an Tests einen erschlägt und man nicht mehr den Code refactored sondern die Tests, vllt. wird es irgendwann soweit kommen, dass man Tests für die Tests anfängt zu schreiben 🙂
Na ja, wie auch immer, heute will ich euch den JMeter näher bringen, wie man mit seiner Hilfe, ganz einfache Tests für eine bestehende Webanwendung schreiben kann, ohne viel Aufwand dafür aufbringen zu müssen.
Zunächst besorgen wir uns JMeter Binaries, nachdem wir es entpackt haben, führen wir die ( jmeter.sh | jmeter.bat ) im Ordner /bin aus, in wenigen Sekunden sollten wir vor unseren Augen die GUI zu sehen bekommen:
Apache-JMeter-Testplan
Als Erstes, wechseln wir die Sprache von Deutsch auf Englisch unter Optionen > Wählen Sie eine Sprache > English, gleich danach mit einem geschickten Maus-Rechtsklick auf den Testplan > Add > Threads (Users) > Thread Group und belasen alles so wie es ist.
Nun folgt ein weiterer Maus-Rechtsklick, diesmal aber auf den WorkBench > Add > Non-Test Elements > HTTP(S) Test Script Recorder und jetzt ändern wir hier ein paar Sachen ab:

  • Global Settings > Port = 8090
  • Test plan content > Target Controller = Testplan > Thread Group
  • URL Patterns to Include > Add = .*

Wir sind fast soweit, wir brauchen jetzt nur noch einen View Results Tree – Listener, also wieder Maus-Rechtsklick auf den Testplan > Add > Listener > View Results Tree.
Das Ganze sieht am Ende dann so aus:
Apache-JMeter-2
Als letzten Schritt müssen wir jetzt nur noch den Proxy im Browser eintragen als Host: localhost und Port: 8090 und die Tests-Aufzeichnung kann beginnen, sobald wir auf den Start Button klicken beim HTTP(S) Test Script Recorder.
Was wollen wir aber testen? Nun, ich habe mich für Icinga Web 2 entschieden, wegen den vielen Requests die im Hintergrund so passieren und folgendes kleines Szenario überlegt:

  • aufrufen der Startseite
  • anmelden
  • nach test-flap-10 suchen
  • test-flap-10 Host aufrufen
  • System Configuration aufrufen
  • History Notifications aufrufen
  • Dashboard Landingpage aufrufen
  • abmelden

Jetzt drücken wir auf den Start Button und rufen im Browser http://localhost:8080/icingaweb auf und gehen das Szenario durch und wenn wir fertig sind, können wir die Test-Aufzeichnung im JMeter über den Stop Button beenden.
Das wars auch schon, die Thread Group wurde mit allen Requests gefüllt die so alles entstanden sind während der Test-Aufzeichnung und können nun auch von JMeter abgespielt werden.
Apache-JMeter-3
Die Requests Ergebnisse können nach dem Abspielen im View Results Tree ausgewertet werden.
Apache-JMeter-4
Sollte kein Request fehlgeschlagen haben, so können wir davon ausgehen, dass die Kommunikation zwischen dem Front- und Backend funktioniert.
Im Großen und Ganzen wie man sieht, lässt sich mit JMeter sehr schnell und einfach ein Test aufzeichnen, abspielen und auswerten und jetz kommt das ABER …
… dafür gibt’s weiter unten die Kommentare 😉
P.S. Humor …

End2End Monitoring mit CasperJS

CasperJS LogoWas ist eigentlich dieses End2End Monitoring? Während viele Nutzer von Icinga haarklein jede Ecke ihrer IT-Landschaft überwachen, und sofort merken sollten wenn ein Dienst ausfällt oder ein Dateisystem voll läuft, werden Funktionstests aus der Endbenutzer-Sicht oft vernachlässigt.
Hier bietet CasperJS einen netten Funktionsrahmen um solche Tests für Webseiten einfach zu machen und z.B. den Inhalt zu prüfen oder mit der Webseite zu interagieren.
Ein kleiner Auszug der Funktionen von Casper:

  • Navigations-Schritte definieren und abarbeiten
  • Formulare ausfüllen und absenden
  • Interaktion mit der Webseite (Buttons und Links)
  • Screenshots zu jedem Zeitpunkt
  • Javascript Code im Context der Webseite (DOM) ausführen
  • Test Funktionen für den Inhalt der Webseite

Den Kern hinter Casper bildet dabei das Browser-Framework PhantomJS, dass einen Webkit Browser simuliert den man über Javascript steuern kann. Der große Unterschied zu einem echten Browser ist nur dass PhantomJS keinerlei Frontend bietet, alles geschieht im Hintergrund, und erfordert somit auch keine Desktop Oberfläche. Als Alternative lässt sich auch SlimerJS nutzen, was einen ähnlichen Funktionsumfang wie Phantom bietet, aber auf der Firefox Engine (Gecko) basiert.
Beide Tools liefern eine komplette Javascript API um den Browser zu steuern, was Casper beisteuert sind viele Hilfsfunktionen, die man sich sonst selbst schreiben müsste.
Aber wie sieht so ein CasperJS Skript eigentlich aus? Hier ein einfaches Beispiel:

/*
 * Webseite www.netways.de
 */
var url = 'http://www.netways.de';
casper.test.begin('NETWAYS.de', 4, function suite(test) {
  casper.start(url, function() {
    test.assertHttpStatus(200, "HTTP Request erfolgreich");
    test.assertUrlMatch(url, "auf der richtigen Webseite");
    test.assertTitleMatch(/NETWAYS/, "Titel der Webseite");
    test.assertExists('a[href="de/info/jobs/"]', 'Link auf Jobs');
  });
  casper.run(function() {
    test.done();
  });
});

Das Ergebnis auf der Kommandozeile sieht dann so aus:
CasperJS www.netways.de
Für einen Kunden integriere ich gerade CasperJS in Icinga, damit man zum einen die Ergebnisse melden, als auch Performancedaten sammeln und auswerten kann. Ich werde berichten sobald es etwas herzeigbares gibt!
Für alle die gerne jetzt, oder am Wochenende, mit CasperJS erste Gehversuche machen folgen noch ein paar Links. Am besten sollte man mit der Beta Version testen – dort sind viele neue Funktionen enthalten.
Weiterführende Links

P.S. Unsere Stellenanzeigen sollte man übrigens wirklich mal “von Hand” checken, Automatisierung ist nicht für alles gut…

Markus Frosch
Markus Frosch
Principal Consultant

Markus arbeitet bei NETWAYS als Principal Consultant und unterstützt Kunden bei der Implementierung von Nagios, Icinga und anderen Open Source Systems Management Tools. Neben seiner beruflichen Tätigkeit ist Markus aktiver Mitarbeiter im Debian Projekt.

Aus der QA-Küche: Selenium IDE an jung-dynamischen IDs

Inzwischen habe ich die wichtigsten Teile von icinga-web erfasst und indiziert (ich erwähnte diesen Vorgang bereits beiläufig) und sogar die meisten der nötigen Testfälle sind be- und geschrieben, wenngleich diese hie- und da Nachbesserungen und Erweiterungen erfahren werden. Den Index selbst halte ich allerdings für schwer verdauliche Kost für Blog-Einträge. Stattdessen serviere ich ein paar Grundlagen im Umgang mit Selenium IDE mit ausgewählten Problemen der Saison, die sich nicht schnell genug auf die Bäume retten konnten.
Ein kurzer Blick auf icinga-web verrät uns: ein paar Eigenheiten machen es einem nicht leicht, automatisierte Tests durchzuführen. Als da wären zu nennen:

  • Recht umfangreiche, dynamische Struktur mit nur einigen wenigen fest vergebenen IDs (mehr für zukünftige Versionen geplant).
  • Zeitkritische, Performance-abhängige Vorgänge (Datenbankzugriff, Authentifizierung)
  • Im Hintergrund (die per Selenium natürlich angesprochen werden können) befindliche, je nach Nutzerrechten variierende und durch Plug-Ins erweiterbare Elemente

Die Basis: Ich hab da mal was vorbereitet …
Einigen Komplikationen lassen sich durch regelmäßiges Zurücksetzen des Benutzerprofils, kurz gehaltenen (ggf. in mehrere Abschnitte aufgeteilte) Testfällen, löschen der Cookies und möglichst wenig geöffneten Modulen vorbeugen. Als Grundlage dient Icinga mit einer leicht modifizierten Testkonfiguration, etwa mit zugefügten Custom Variables und geänderten „Flapping“-Einstellungen, um nachvollziehbare Werte zu erhalten. Neue Commits erkennt und installiert Jenkins CI automatisch, initialisiert die Datenbank neu, entfernt Überbleibsel wie Logs, Cachedateien etc. und startet schließlich der Selenium Server mit unserer Test-Suite.
Ja, wo laufen sie denn – Verwirrung (vor)programmiert
Trotz aller Vorsichtsmaßnahmen besteht permanent Verwechslungs- und „Nicht-Wiedererkennungs“-Gefahr von Elementen, egal ob nun per ID, xPath, Text oder gar css-Pfad Identifiziert. Da ist es immer wieder erfrischend, was bei vermeintlich unfehlbaren, simplen Click&Verify-Prüfungen alles schief gehen kann. Für die Vorspeise nehmen wir (also das „Kinderarzt-wir“) uns einen Harmlosigkeit heuchelnden „OK“-Knopf zur Brust, den wir (ver)drücken wollen. Bei der Aufzeichnung ermittelt Selenium IDE kurz und bündig:
//div/div/div/div[2]/div/table/tbody/tr/td[2]/table/tbody/tr/td/table/tbody/tr/td/table/tbody/tr[2]/td[2]/em/button
Das ist zum einen weder beim durchsehen der Test-Cases selbst als auch dem Testprotokoll sonderlich aussagekräftig. Zum anderen könnten schon kleinen Änderungen an unserem Test oder der Software eine Korrektur des Pfads nötig machen. Also gestalten wir das ganze ein wenig „unschärfer“, nach dem Motto: So genau wie nötig, so ungenau wie möglich (Hoho! Aufgemerkt – als Lehrer wäre ich große Klasse im Dumme-Lehrsprüche-klopfen …). Mit Firebug den Code betrachtet, kommen wir auf
<button id=”ext-gen225″ type=”button” style=”background-color: transparent;”>OK</button>
wobei die id=”ext-gen225″ wie erwähnt lustig wechselt. Dagegen lässt sich die class selbst, wenn der Knopf irgendwo anders in der Struktur landet, mit Selenium finden. Als Target sieht das so aus:
//*/em/button[contains(@class, ‘x-btn-text icinga-action-icon-ok’)]
contains ist in diesem speziellen Fall streng genommen nicht erforderlich. Es ist der exakte Wert und verändert sich nicht. Ebenso könnte man sich /em/button sparen. Es dient der Veranschaulichung. Ganz banal könnten wir auch nach dem Text des Knopfes (oder eines Links) fahnden (beides targets):
link=OK
oder
//*[.=’OK’]
Trotz allem versagen diese zwei Varianten, sobald ein weiteres Knopf-Pendant mit den gesuchten Attributen vorhanden ist (was des öfteren vorkommt) und wir würden immer das in der Struktur weiter oben angesiedelte „OK“ erwischen.
Direkt ist besser
Bei Funktionen, die lediglich Mittel zum Zweck sind (d.h. nicht einen vorhandenen Dialog, Knopf etc. direkt prüfen soll), etwa „Ausloggen“, können wir zwar einen irgendwo im Code vorhandenen Link via target
//a[contains(@href, ‘/icinga-web/modules/appkit/logout?logout=1’)]
ausfindig machen und ansprechen. Narrensicher ist es dagegen, die URL direkt aufzurufen oder Seleniums deleteAllVisibleCookies dafür zu missbrauchen. Seite anschließend neu laden, fertig.
Hin und wieder leistet bei dem zwar bequemen, aber eingeschränkten Selenium IDE ein eingestreuter JavaScript-Befehl gute Dienste, wie letztlich im Code der Seite zu finden. Hier ein Beispiel, ob die Zeitmarken der gewählten Zeitzone entsprechen:

Command Target Value
store javascript{new Date().getHours()}  localtime
verifyText //td/div/div/div  *${localtime}*



Oder hier ein direkter Aufruf der Sucheingabe und setzten des Fokus darauf:

Command Target Value
runScript AppKit.search.SearchHandler.getSearchbox().showSearch();
runScript AppKit.search.SearchHandler.getSearchbox().focus();

Alles zu seiner Zeit
Selenium haut die Befehle teils so schnell raus, dass es bei diversen Gelegenheiten wie Markieren, Abhaken, Tastatureingaben und anschließendem Speichern von Einstellungen zu Geschmacksverirrungen kommen kann. Zwar kann es fruchten, die Ausführungsgeschwindigkeit mit setSpeed für bestimmte Abschnitte herabzusetzen oder Pausen zu setzen. Die bessere Lösung ist es jedoch für Gewöhnlich, anderweitig zu sichern, dass die gewünschten Elemente, Eingaben etc. vorhanden sind. Denn wie langsam es laufen muss und darf, ist ein Schätzwert, der je nach Serverauslastung auch mal schwanken kann. Zudem läuft nach einem Fehlschlag womöglich der Rest der Test ebenfalls nur sehr langsam weiter.
Ob ein Element vorhanden ist, genügt oft nicht als Kriterium, beispielsweise, wenn sich dieses im Hintergrund befindet, wie die Globale Suche. Neben Befehlen wie waitForVisible bieten sich als Alternative oft Werte wie attributes, values, positions … zu Prüfung an, die sich bei dem Vorgang ändern. Wir lassen Selenium erst lostippen, wenn das Suchfeld unserer Aufmerksamkeit gewiss ist:

Command Target Value
waitForAttribute name=global_search@class x-form-text x-form-field x-form-focus

Oder lassen warten, bis die Daten unseres Test-Users tatsächlich geladen sind und in den Feldern erscheinen:

Command Target Value
waitForValue name=user_name test

Da alle fünf Minuten die Authentifizierung erneuert wird, kann es vorkommen, dass in einem ungünstigen Moment gesendete Kommandos fehlschlagen. Dem wirken wir mit einem per flowControl-Erweiterung zubereiteten Nachtisch entgegen und senden bei einem Fehlschlag einfach erneut (und drücken die Daumen, nicht ewig der Schleife zu hängen ;)):

Command Target Value
label retrysend
clickAt css=div.x-grid3-row-checker 1,1
clickAt //button[.=&quot;Commands&quot;] 1,1
waitForVisible //*[contains(@class, ‘x-menu-item-icon icinga-icon-comment’)]
clickAt //*[contains(@class, ‘x-menu-item-icon icinga-icon-comment’)] 1,1
waitForTextPresent persistent
type name=comment testcomment
clickAt name=comment 10,10
clickAt //*[contains(@class,’x-btn-text icinga-icon-accept’)] 10,10
storeText //div[2]/div/div/div/div/div/div[2]/span
while storedVars.fail == ‘Authentification failed’
click //*[.=’OK’;]
goto retrysend
endWhile