Blog

Testing-AntiPattern-Kalender 2014 – Oktober – Der Lokalmatador

In der monatlichen Blog-Reihe zu unserem Anti-Pattern-Kalender 2014 zum Thema Testing hier der Kandidat für den Monat Oktober: Der Lokalmatador.

Nach mir die Sintflut…

Der Lokalmatador ist ein Held. Allerdings nur zu Hause. In fremden Laufzeitumgebungen fühlt er sich nicht so wohl.

Solltest du zu den Entwicklern gehören, die ihre Anwendungen in aktuellen Programmiersprachen wie Java, C#, Scala oder mit einer JavaScript-Technologie (z.B. AngularJS oder JQuery) entwickeln, dann kannst du dich glücklich schätzen: schließlich entwickelst du plattformunabhängig. Das ist sicher und zukunftsweisend. Welche Plattform die Benutzer auch verwenden, das kann dir egal sein. Die armen C++-Entwickler können einem auch leid tun.

Nur, dass du mit dieser Einschätzung ungefähr so weit daneben liegst, als würdest du die Insel Java im Ostfriesischen Wattenmeer vermuten.

Lass uns doch einmal kurz über Plattformunabhängigkeit nachdenken…

Zunächst einmal: Was ist die Plattform?

Die Plattform ist, vereinfacht gesagt, die Kombination aus Hardware, Betriebsystem und installierter Software, die dein Programm zum Laufen befähigt. In den allermeisten Fällen dürften wir an unserem Arbeitsplatz einen x86-Prozessor vorfinden, auf dem Windows oder ein Unix-artiges Betriebssystem läuft (Linux oder Mac OS). Die Smartphones lassen wir mal außen vor…

Wahrscheinlich brauchst du außerdem, um deine Software laufen zu lassen, eine VM oder einen Browser als Laufzeitumgebung. Bei den Browsern gibt es schon mehr Vielfalt: Chrome, Firefox oder Internet Explorer. Browser mal Betriebssysteme ergibt neun Arten von Umgebungen, in denen deine Software laufen muss.

Für ein Programm, welches völlig abgeschottet in seiner Laufzeitumgebung vor sich hin werkeln kann, mag das unkritisch sein. Spannend wird es aber, wenn dein Programm irgendwie Kontakt zur Außenwelt herstellen muss. Und das tun die meisten Programme eigentlich ziemlich häufig

Mit der Plattformunabhängigkeit ist es nämlich vorbei, wenn dein Programm:

  • Dateien verarbeitet: die Pfade sind bei Windows und den Unixen unterschiedlich, außerdem noch die Längenbegrenzungen für die Namen, sowie der Umgang mit Groß- und Kleinschreibung
  • Zeilenumbrüche verarbeitet oder erzeugt: Line Feed kontra Carriage Return. Manchmal wird LF verwendet, manchmal CR, manchmal beides.
  • Umgebungsvariablen abfragt: unter Windows: %JAVA_HOME%, unter Unix: $JAVA_HOME
  • externe Programme aufruft: auch hierfür sind die Befehle unter den einzelnen Betriebssystemen unterschiedlich
  • oder einfach HTML-5-Elemente im Browser darstellen soll: Und für gestandene Webdesigner ist es natürlich nichts neues, dass 40% der Entwicklungszeit dafür verbraten werden, das ganze auch noch im IE 8 zum Laufen zu bringen.

Es wird noch netter

Befinden deine Benutzer sich in einem anderen Land als du, wird es schwierig, wenn dein Programm:

  • Datum und Uhrzeit anzeigt: 03.10.2014 oder 03/10/2014 oder Oct 3, 2014
  • Dezimalzahlen oder Tausendertrennzeichen darstellt: 1,000,000,000.000 = One Billion = eine Milliarde
  • Entfernungen, Flüssigkeitsmengen, Gewichte oder Temperaturen berechnet: schon einmal einen amerikanischen Wetterbericht gesehen und sich über die Gelassenheit der Moderatorin gewundert, während sie Temperaturen jenseits der 60-Grad-Marke vorträgt? Insbesondere die USA und Großbritannien verweigern sich bis heute dem metrischen System und vertrauen lieber auf ein System mit Vielfachen von drei.
  • Währungen konvertiert: wie im vorherigen Punkt, nur mit tagesaktuellen Kursen, und Rundungsdifferenzen kosten hier Geld

… manchmal müssen wir auch nach draußen telefonieren …

  • Externe Ressourcen:
    Ressourcen, wie Datenbanken und WebServices können sich auf anderen physischen Maschinen befinden. Diese müssen adressiert werden, im einfachsten Fall geschieht dies über IP-Adressen (die ja bekanntlich auf lange Zeit festgeschrieben sind).
  • Ports und Netzwerkinfrastruktur: Viele Firmennetze schränken den Zugriff der Clients aufs Internet stark ein, um die Benutzer am Herunterladen von z.B. Katzenvideos zu hindern. Für uns bedeutet das, dass unser schön auf Port 80 getesteter Webservice im Realeinsatz möglicherweise nicht mehr erreichbar ist und die Testfälle das nicht vorhergesehen haben.

… aber zuhause ist es doch am gemütlichsten (My Host is my Castle)

Und dann gibt es noch jede Menge lokale Gegebenheiten, auf die dein Test sich verlässt, ohne dass du es merkst (und weil du auch nicht drüber nachgedacht hast):

  • installierte Tools: greift der Test auf grunt, phantomJS, etc. zu?
  • vorhandene Dateien I: fragt dein Test Konfigurationsdateien ab oder greift er auf Bibliotheken zu, die nur innerhalb deines Klassenpfades existieren?
  • vorhandene Dateien II: setzt dein Test bestimmte Verzeichnisse oder Verzeichnisinhalte voraus?
  • fest codierte Verbindungen I: greift dein Test auf eine Datenbank zu, die lokal auf deinem Rechner liegt?
  • fest codierte Verbindungen II: Ist die URL einer Datenbank oder eines Webservices hart kodiert?
  • Schmutzige Tricks: Wenn dein Programm den
    Shellshock-Bug ausnutzt oder sich anderer interessanter Workarounds bedient, dann darfst du dich zu Recht fragen, ob das beim Kunden laufen wird.

Und dafür sollen wir nun Tests schreiben….

Stellen wir uns einmal vor, wir möchten für einen der oben geschilderten Fälle einen Test schreiben. Zunächst einmal werden wir natürlich testen, ob die Funktionalität auf unserer Entwicklungskiste läuft. Dann sollten wir prüfen, welche der von uns verwendeten Parameter oder Konfigurationen auf einer anderen Plattform ein Problem darstellen können.

Es bieten sich verschiedene Möglichkeiten an, um das ganze konsistent zu halten:

Bleibe so allgemein wie möglich!

Wenn wir eine Datei speichern möchten, können wir den Namen so schreiben, dass er auf allen Betriebssystemen funktioniert: nur ASCII-Zeichen, keine Leerstellen, einen kurzen Namen und vor allem keine absoluten Pfade. Letztere dürften bereits ein Problem darstellen, wenn der Test auf einem anderen Rechner mit dem gleichen Betriebssystem laufen soll

Verwende Polyfills!

Wenn wir es mit verschiedenen Browsern zu tun haben, bietet es sich bei der Entwicklung einer Webanwendung an, sogenannte Polyfill-Bibliotheken zu verwenden. Diese bringen Elemente mit, die speziell für den Einsatz in verschiedenen Browsern entwickelt wurden.

Extrahiere die zu testende Information!

Wenn wir testen, ob zwei Euro mal zwei Euro vier Euro ergibt, dürfte sich das auf Dollarwerte übertragen lassen. Unter Umständen kann der Währungsbezeichner also beim Test vernachlässigt werden und wir prüfen nur, ob zwei mal zwei vier ergibt.

Noch ein Wort zu Währungen …

insbesondere bei Währungen ist außerdem darauf zu achten, ob das Programm eine Währungsumrechnung in die Stammwährung vornimmt – und vor allem, wann es dieses tut. Werden Fremdwährungsbeträge nämlich konvertiert, bevor sie summiert werden, können sich hier gewaltige Rundungsdifferenzen ansammeln (ich habe schon solche Programme gesehen). Auch wenn hier eigentlich ein Designfehler vorliegt: es wird schwer, herauszufinden, warum der Test hier fehlschlägt.

Nutze die nativen Funktionen deiner Programmiersprache!

Möchtest du beispielsweise Umgebungsvariablen abfragen, bietet Java dir dafür eine eigene Funktion:

String javaHome = System.getenv("JAVA_HOME");

Du brauchst dich also nicht mit systemspezifischen Gegebenheiten herumzuschlagen.

Kapsele lokale Gegebenheiten in Umgebungsvariablen!

Lokale Besonderheiten, insbesondere Pfade zu bestimmten Programmen oder Dateien, hinterlegst du am besten in Umgebungsvariablen auf dem jeweiligen System. Vergiss aber nicht, in deinen Test eine Warnung einzubauen für den Fall, dass die Variable nicht gesetzt sein sollte.

Denke international!

Wenn du beispielsweise Datumswerte testest, solltest du auf gar keinen Fall die Ausgabe in einen String konvertieren und dann mit deinem Erwartungswert vergleichen. Sondern den Erwartungswert gleich als Datum formulieren:

        DateFormat df = new SimpleDateFormat("dd/MM/yyyy");
        Date thirdOctober = df.parse("03/10/2014");

Baue Weichen ein!

Plattformspezifische Parameter in Konstanten auslagern und beim Test die Plattform des Wirtssystems abfragen und danach die Konstante auswählen (eine sogenannte „Weiche“)

const ROOT_WINDOWS = "C:\";
const ROOT_UNIX = "/"
if (system == "windows") {
root = ROOT_WINDOWS;
} else if (system == "linux") {
root = ROOT_UNIX;
}

Verwende dynamische Ressourcenbezeichner!

Beim Zugriff auf Webressourcen ist eine elegante Lösung die Einrichtung einer URL – unsere Datenbank könnte dann wie folgt angesprochen werden:
mysql://dbserver.holisticon.de/customerDB

Dann können wir die Datenbank auf einen anderen Server umziehen, ohne großen Konfigurationsaufwand betreiben zu müssen. Lediglich der DNS-Eintrag muss geändert werden. Dein freundlicher SysOp hilft dir gerne weiter.

noch besser: Mocke externe Ressourcen!

Noch eleganter ist es, Datenbanken und Webservices zu mocken. Hierbei wird der Test mit Objekten oder Dateien versorgt, die so ausssehen, als kämen sie von der Datenbank oder vom Webservice. In Wahrheit sind diese aber innerhalb der Testumgebung hinterlegt. Damit bist du dann auch von der Eventualität befreit, dass die Datenbank etwas anderes liefert als erwartet.

Und zum Schluss: Teste unter verschiedenen Plattformen!!!

Wichtig ist vor allem, dass du auch unter verschiedenen Plattformen testest. Es lohnt sich zu evaluieren, welchen Plattformtyp die Zielgruppe einsetzt. Diesen solltest du dann in deine Tests einbeziehen, um „Überraschungen“ zu vermeiden. Wenn ein Großteil der Benutzer sich einem bestimmten Setup (Plattform und Netzwerk) zuordnen lässt, können wir auch einmal darüber nachdenken, die Benutzerumgebung zu Testzwecken nachzubauen. Eher schwach ist wohl die Ausrede, man hätte nicht daran gedacht, dass an Bankschaltern nur Windows-Rechner eingesetzt werden…

Was haben wir heute gelernt?

Wir haben heute den Mythos der Plattformunabhängigkeit gelüftet, uns mit jeder Menge Reisewissen herumgeschlagen und schlussendlich gelernt, wie wir mit der Vielfalt in der Welt der Betriebssysteme umgehen können. Fehler lassen sich natürlich nie ausschließen. Aber auf ein Mindestmaß reduzieren :)

In diesem Sinne wünschen wir dir: frohes Testen!

Über den Autor

Antwort hinterlassen