JUnit 4 Tutorial, Java-Tests mit JUnit

Hinweis! Eine aktuelle Version des Abschnittes gibt es im Buch „Java ist auch eine Insel. Einführung, Ausbildung, Praxis“, dem 1. Inselbuch. (War für den 2. Band vorgesehen, musste wegen der Fülle des 2. Buchs kurzerhand in den 1. Band geschoben werden. Sorry für Irritationen!)

1.1 Softwaretests

Um möglichst viel Vertrauen in die eigene Codebasis zu bekommen, bieten sich Softwaretests an. Tests sind kleine Programme, die ohne Benutzerkontrolle automatisch über die Quellcodebasis laufen und anhand von Regeln zeigen, dass gewünschte Teile sich so verhalten wie gewünscht. (Die Abwesenheit von Fehlern kann eine Software natürlich nicht zeigen, aber immer zeigt ein Testfall, dass das Programm die Vorgaben aus der Spezifikation erfüllt.)

Obwohl Softwaretests extrem wichtig sind, sind sie unter Softwareentwicklern nicht unbedingt populär. Das liegt unter anderem daran, dass sie natürlich etwas Zeit kosten, die neben der tatsächlichen Entwicklung aufgewendet werden muss. Wenn dann die eigentliche Software geändert wird, müssen auch die Testfälle oftmals mit angefasst werden, so dass es gleich zwei Baustellen gibt. Und da Entwickler eigentlich immer gestern das Feature fertig stellen sollten, fallen die Testes gerne unter den Tisch. Ein weiterer Grund ist, dass einige Entwickler sich für unfehlbare Codierungsgötter halten, die jeden Programmcode (nach ein paar Stunden debuggen) für absolut korrekt, performant und wohl duftend halten.

Wie lässt sich diese skeptische Gruppe nun überzeugen, doch Tests zu schreiben? Ein großer Vorteil von automatisierten Tests ist die Eigenschaft, dass bei großen Änderungen der Quellcodebasis (Refactoring) die Testfälle automatisch sagen, ob alles korrekt verändert wurde. Denn wenn nach dem Refactoring, etwa einer Performance-Optimierung, die Tests einen Fehler melden, ist wohl etwas kaputt-optimiert worden. Da Software einer permanenten Änderung unterliegt und nie fertig ist, sollte das Argument eigentlich schon ausreichen, denn wenn eine Software eine gewisse Größe erreicht hat, ist nicht absehbar, welche Auswirklungen Änderungen an der Quellcodebasis nach sich ziehen. Dazu kommt ein weiterer Grund, sich mit Tests zu beschäftigen. Es ist der positive Nebeneffekt, dass die erzeugte Software vom Design deutlich besser ist, denn testbare Software zu schreiben ist knifflig, führt aber fast zwangsläufig zum besseren Design. Und ein besseres Design ist immer erstrebenswert, denn das erhöht die Verständlichkeit und erleichtert die spätere Anpassung der Software.

1.1.1 Vorgehen bei Schreiben von Testfällen

Die Fokussierung bei Softwaretests liegt auf zwei Attributen: automatisch und wiederholbar. Dazu ist eine Bibliothek nötig, die zwei Dinge unterstützen muss.

· Testfälle sehen immer gleich aus und bestehen aus drei Schritten. Zunächst wird ein Szenario aufgebaut, dann die zu testende Methode oder Methodenkombination aufgerufen und zum Schluss mit der spezifischen API vom Testframework geschaut, ob das ausgeführte Programm genau das gewünschte Verhalten gebracht hat. Das Übernehmen eine Art „stimmt-es-dass“-Methoden, die den gewünschten Zustand mit dem tatsächlichen Abgleichen und beim Konflikt eine Ausnahme auslösen.

· Das Testframework muss die Tests laufen lassen und im Fehlerfall eine Meldung geben; der Teil nennt sich Test-Runner.

Wir werden uns im Folgenden auf sogenannte Unit-Tests beschränken. Das sind Tests, die einzelne Bausteine (engl. units) testen. Daneben gibt es andere Tests, wie Lasttests, Performance-Tests oder Integrationstests, die aber im Folgenden keine große Rolle spielen.

Weiterlesen

Java Decompiler: JD, Jad

Der Java-Compilers erzeugt aus der Quellcodedatei eine Klassendatei und der Decompiler dreht die Arbeitsweise um. Decompiler gibt es für die verschiedenen Programmiersprachen und Java gehört zu den Sprachen, bei der die Zurückübersetzung einfacher ist als bei optimierten Maschinenprogrammen, die zum Beispiel ein C++-Compiler erzeugt. Der Grund ist, dass im Bytecode viele wertvolle Informationen enthalten sind, die in herkömmlichen Maschinencode nicht auftauchen. Darunter sind etwa Typinformationen oder Hinweise, ob ein Methodenaufruf virtuell ist oder nicht. Sie sind für die Java-Laufzeitumgebung wichtig und eine große Hilfe, wenn es darum geht, mit einem Decompiler verlorenen Quellcode wiederzubeleben oder an fehlende Informationen aus Paketen von Fremdherstellern zu gelangen.

Ein Decompiler liest die Klassendatei als Bytefeld ein und beginnt mit der Analyse. Da der Bytecode gut dokumentiert ist, ist das Extrahieren von Variablen- oder Methodennamen einfach. Schwierig sind die Anweisungen. Aus dem Java-Bytecode für eine Methode baut ein Decompiler einen Kontrollfluss-Graphen auf und versucht, Anweisungen und Ausdrücke zu erkennen, die bei der Übersetzung bestimmter Sprachkonstrukte entstanden sein müssten. Das ist eine nicht-triviale Aufgabe und immer noch Gegenstand einiger Diplomarbeiten. Und da Variablennamen durch einen Obfuscator eventuell ungültig gemacht worden sind, muss ein guter Decompiler diese illegalen Bezeichnernamen korrigieren und weitere Tricksereien vom Obfuscator rückgängig machen. Diese Umbenennung ändert den Algorithmus nicht, und ein Decompiler hat es bei dieser Art von Verschleierung einfach.

Ist das legal?   Lassen wir einen Decompiler auf den eigenen Programmcode los, weil etwa der Quellcode verschwunden ist, dann ist die Anwendung kein rechtliches Problem. Das Reverse Engineering von vollständigen Anwendungen, die unter Urheberschutz stehen, muss nicht unbedingt ein Problem darstellen. Vielmehr beginnt die Straftat, wenn dieser Quelltext verändert und als Eigenleistung verkauft wird.

Da mittlerweile auch andere Compiler auf dem Markt sind, die Java-Bytecode erzeugen – etwa aus EIFFEL-Programmen oder aus diversen LISP-Dialekten –, ist über den Umweg Compiler/Klassendatei/Decompiler ein Crosscompiling denkbar. Hier sind jedoch einige Einschränkungen bezüglich der auf dem Markt befindlichen Decompiler erkennbar. Denn fremde Compiler, die Java-Bytecode erstellen, haben andere Techniken, die der Decompiler dann nicht immer passend übersetzen kann.

Java Decompiler project (JD) und Alternativen

Der Markt an leistungsstarken Decompilatoren ist sehr übersichtlich. Das beste Tool (aber auch nicht ganz fehlerfrei) ist zurzeit JD (http://java.decompiler.free.fr/). Das frei verfügbare – aber nicht quelloffene – Programm ist als Bibliothek JD-Core, als alleinstehende grafische Anwendung JD-GUI und Eclipse-Plugin  JD-Eclipse verfügbar. JD selbst ist in C++ geschrieben und benötigt daher keine JVM. JD verarbeitet den Bytecode verschiedener Compiler, wobei das JDK 1.1 bis JDK 6 selbstverständlich mit in der Liste ist, genauso wie der Eclipse-Compiler. (Die Unterscheidung ist nicht ganz uninteressant, da die Compiler sich in machen Details in der Bytecode-Abbildung doch unterscheiden.) JD-GUI ist für die Plattformen Windows, Linux und Mac unter dem Punkt http://java.decompiler.free.fr/?q=jdgui#downloads verfügbar und bietet neben dem Decompileren einzelner Java-Klassen und ganzen Java-Archiven eine angenehme Quellcodedarstellung mit farblicher Unterlegung und Drag & Drop.

JD

Sehr lange war der Decompiler Jad die Referenz. Doch nur von 1997 bis 2001 hat Pavel Kouznetsov das Kommandozeilenprogramm in C++ entwickelt und dann auch 2009 seine Webseite vom Netz genommen. Eine Privatperson hat jedoch die Webseite gespiegelt und unter http://www.varaneckas.com/jad lebt das Projekt (auf unbestimmte Zeit) weiter. Wer Projekte bis Java 1.4 decompilieren möchte, ist mit dem Tool sehr gut bedient. Für Java 5 Projekte hilft JadRetro (http://jadretro.sourceforge.net/)  noch ein wenig nach, in dem es Java 5 Bytecode auf Java 1.4 anpasst und kleine Änderungen im Bytecode durchführt. FrontEnd Plus ist eine grafische Oberfläche für Jad, doch auch sie ist vom Internet verschwunden, seit dem es Jad nicht mehr offiziell gibt. Unter http://jadclipse.sourceforge.net/ ist auch ein Plugin für Eclipse erhältlich, dessen Ende aber ebenfalls eingeläutet ist.

JSON-Serialisierung mit Jackson

Nehmen wir folgende Zeile JavaScript-Code, das ein Person-Objekt mit zwei Properties für Name und Alter definiert. Eine Property wird über ein Schlüssel/Werte-Paar beschrieben:

var person = { „name“ : „Michael Jackson“, „age“ : 50 };

Die Definition eines Objekts geschieht in der JSON (JavaScript Object Notation). Als Datentypen unterstützt JSON Zahlen, Wahreiswerte, Strings, Arrays, null und Objekte – wie unser Beispiel zeigt. Die Deklarationen können geschachtelt sein, um Unterobjekte aufzubauen.
Zum Zugriff auf die JSON-Daten kommt der Punkt zum Einsatz, sodass der Name nach der Auswertung durch person.name zugänglich ist.

Eine Personenbeschreibung wie diese kann auch in einem String stehen, die von JavaScript zur Laufzeit ausgewertet wird.

var json = ‚person = { „name“ : „Michael Jackson“, „age“ : 50 };‘;
eval( json );

Der Zugriff auf person.name liefert wie vorher den Namen, denn nach der Auswertung mit eval() wird JavaScript ein neues Objekt mit person im Kontext anlegen.

JSON ist besonders praktisch, wenn es darum geht, Daten zwischen einem Server und Browser mit JavaScript-Interpreter auszutauschen. Denn wenn der String json nicht von Hand mit einem String initialisiert wurde, sondern ein Server die Zeichenkette person = { … }; liefert, haben wir das, was heutzutage in modernen Ajax-Webanwendungen passiert. Die letzte Frage ist nun, wie elegant der Server Zeichenketten im Datenaustauschformat JSON erzeugt und so Objekte überträgt. Den String per Hand aufzubauen ist eine Lösung, aber es geht besser.

Die Open-Source Bibliothek  Jackson (http://jackson.codehaus.org/) gehört zu den populärsten Lösungen, die JSON-Daten einliest und ausgibt und auf JavaBeans überträgt, sodass eine unkomplizierte Serialisierung in JSON möglich wird.

ObjectMapper mapper = new ObjectMapper();
MyClass myObject = mapper.readValue( input, MyClass.class );
mapper.writeValue( output, myObject );

Der ObjectMapper übernimmt das Lesen/Schreiben. In der zweiten Zeile wird aus der Eingabequelle input gelesen und ein Objekt vom Typ MyClass rekonstruiert. In der dritten Zeile wird es in die Ausgabe output geschrieben.

JSON ist nicht nur für die Objektübertragung zwischen Server und Browser gut, sondern ist eine elegante Alternative zu XML, wenn es etwa um lokale Konfigurationsdateien geht. JSON ist viel kürzer als XML und kann somit zum Beispiel für Konfigurationsdateien übersichtlicher sein.

Bean-Zustände kopieren

In mehrschichtigen Anwendungen gibt es oft das Muster, dass eine JavaBean etwa über eine Objekt-Relationale-Mapping-Technologie automatisch aus einer Datenbankzeile aufgebaut wird und dann internen in der Geschäftsschicht verwendet wird. Soll nun diese Information über das Netzwerk an einen anderen Rechner verteilt werden, ist es nicht immer angebracht, diese JavaBean etwa direkt über Serialisierung zu versenden. Stattdessen kann ein Transfer-Objekt aufgebaut werden, eine spezielle JavaBean zum Beispiel, sodass der Empfänger keine Abhängigkeit zu der Bean in der internen Geschäftsschicht hat. Nun werden sich aber diese Geschäftsschicht-Bean und Transfer-Bean sehr ähnlich sein und viele Entwickler scheuen die Mühe, lästigen Kopiercode zu erstellen. Doch manuelle Arbeit ist nicht nötig und eine Lösung  für das Kopierproblem ist über Refection schnell geschrieben. Über die BeanInfo kommen wir an den PropertyDescriptor (siehe dazu „Properties einer Bean erfragen“) und dann liefern getReadMethod() und getWriteMethod() die Setter/Getter. Bei einer eigenen Kopiermethode wie copyProperties(Object source, Object target) müssen wir bei der Quell-Bean jede Property auslesen und entsprechend beim Ziel-Bean nach der Property suchen und den Setter aufrufen. Wenn das ganze ohne Typkonvertierungen programmiert werden soll, sind es nur wenige Zeilen Programmcode. Kommen einfache Konvertierungen dazu, etwa wenn einmal ein Wrapper als Property-Typ genutzt wird und einmal der primitive Datentyp, ist es etwas mehr.
Der Aufwand mit einer eigenen Implementierung ist allerdings nicht nötig, denn zwei populäre Implementierungen können helfen:
•    Apache Commons BeanUtils (http://commons.apache.org/beanutils/). Die Klasse org.apache.commons.beanutils.BeanUtils bietet praktische statische Methoden wie copyProperty(Object bean, String name, Object value), copyProperties(Object dest, Object orig), Object     cloneBean(Object bean) oder populate(Object bean, Map properties).
•    Dozer (http://dozer.sourceforge.net/). Dozer bringt ausgefeilte Mapping-Möglichkeiten mit, die weit über BeansUtils hinausgehen. Das geht soweit, dass es ein Eclipse-Plugin zur Konfiguration der Abbildungen gibt.

Inselraus: getContextClassLoader() vom Thread

Entwickler von Java-Enterprise-Applikationen haben oft damit zu kämpfen, dass immer der falsche Klassenlader eine Klasse bezieht und die Typen dann nicht zusammenpassen. In unserem Beispiel mit dem statischen Initialisierungsblock ist gut zu erkennen, dass durch das zweimalige Laden die Laufzeitumgebung auch zweimal die Anweisungen ausführt. Hätten wir Singletons definiert, würden ihre statische Anfragemethoden unterschiedliche, nicht kompatible Objekte liefern, obwohl es laut Defini-tion eines Singletons der Fall sein müsste. Allgemein gesprochen: Besonders Fabrikfunktionen liefern bei mehreren Versionen der Klasse unterschiedliche Objekte, die nicht zusammenpassen. Zwei Lösungen gibt es hier: Zum einen bekommen die Fabrikfunktionen einen Klassenlader, in dessen Kontext sie die Klassen erzeugen können, oder sie nutzen den Klassenlader, der mit einem Thread ver-bunden ist.

Jeder Thread ist mit einem Klassenlader assoziiert, der standardmäßig mit dem Standardklassenlader identisch ist. getContextClassLoader() auf dem Thread-Objekt bezieht diesen Klassenlader:

Listing 8.13    com/tutego/insel/lang/ThreadClassLoader.java, main()
ClassLoader loader = Thread.currentThread().getContextClassLoader();
System.out.println( loader );  // sun.misc.Launcher$AppClassLoader@a12a00
loader = ThreadClassloader.class.getClassLoader();
System.out.println( loader );  // sun.misc.Launcher$AppClassLoader@a12a00

Soll der assoziierte Klassenlader geändert werden, bewerkstelligt dies setContextClassLoader().