Donnerstag, 31. Oktober 2013

JUnit Rules

Vor einiger Zeit bin ich zufällig über eine weniger bekannte Funktionalität von JUnit gestoßen: JUnit Rules.

Das Problem

Mit dem Rules Konstrukt verfügt JUnit seit einer ganzen Weile über die Möglichkeit, den Testablauf zu beeinflussen, also z.B. vor oder nach einem Test etwas zu tun.
Das klingt erstmal absolut unspannend, immerhin sind Methoden, die mit @Before und @After Annotationen versehen werden nicht gänzlich unbekannt. Also was soll das Ganze?

Versuchen wir es mal mit einem einfachen Beispiel:

public class SimpleTest {
@Before
public void before(){
System.out.println("vor dem Test...");
}
@After
public void after(){
System.out.println("nach dem Test...");
}
@Test
public void someTestCode(){
System.out.println("test");
}
}
view raw SimpleTest.java hosted with ❤ by GitHub
Das Ergebnis davon sieht wenig überraschend wie folgt aus:

vor dem Test...
test
nach dem Test...


Nehmen wir an, uns gefällt was in den before() und after() Methoden passiert und wir möchten das wiederverwenden. Nichts einfacher als das:

public class ExtendedTest extends SimpleTest {
@Test
public void moreTestCode(){
System.out.println("extended test");
}
}
Das Ergebnis der Ausführung dieser Klasse mit Hilfe von JUnit sieht wie folgt aus:

vor dem Test...
extended test
nach dem Test...
vor dem Test...
test
nach dem Test...


Durch Vererbung von JUnit Tests erhalten wir ein Verhalten, was nicht in jedem Fall gewünscht ist. Zunächst einmal werden alles Tests der Oberklasse ebenfalls ausgeführt. Des Weiteren haben wir keine Möglichkeit z.B. nur die before() Logik zu nutzen, ohne ebenfalls die Logik innerhalb der after() Methode auszuführen. Das Schränkt die Wiederverwendbarkeit stark ein und führt zu unübersichtlichen Klassenhierarchien.

Die Lösung: JUnit Rules

Mit Hilfe von JUnit Rules lässt sich das Problem elegent lösen. Die Rules stellen einen mit Interceptoren oder Aspekten vergleichbaren Mechanismus bereit unabhängig von der Klassenhierarchie zusätzliche Logik in den Testablauf einzubauen.

Wie funktioniert es? 

Um eine Rule zu definieren muss eine Klasse erstellt werden, die das Interface org.junit.rules.TestRule implementiert. Darin ist nur die Methode apply(Statement base, Description description) definiert. Hinter dem Statement-Objekt verbirgt sich der laufende JUnit Test welcher mit base.evaluate() ausgeführt werden kann. Davor und danach kann eigene Logik ausgeführt werden. Dies könnte z.B. wie folgt aussehen:

public class MyRule implements TestRule {
@Override
public Statement apply(final Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
System.out.println("vor dem Test...");
base.evaluate();
System.out.println("nach dem Test...");
}
};
}
}
view raw MyRule.java hosted with ❤ by GitHub
Um die Rule im eigenen JUnit Test verwenden zu können wird diese einfach als Klassenvariable instanziiert und mit der @Rule Annotation versehen:
public class SimpleTest {
@Rule
public MyRule methodeRule = new MyRule();
@Test
public void someTestCode(){
System.out.println("test");
}
}
view raw SImpleTest.java hosted with ❤ by GitHub

Das Ergebnis des Tests sieht wie folgt aus:

vor dem Test...
test
nach dem Test...


Die Rule wird beim Aufruf jeder einzelner Test-Methode verwendet, analog zu @Before bzw. @After. Alternativ lässt sich die Rule einmalig für die Testklasse verwenden, hierfür ist nur die folgende Änderung notwendig:
public class SimpleTest {
@ClassRule
public static MyRule methodeRule = new MyRule();
@Test
public void someTestCode(){
System.out.println("test");
}
}
view raw SimpleTest.java hosted with ❤ by GitHub

JUnit bringt bereits einige vorgefertigte Rules mit, siehe https://github.com/junit-team/junit/wiki/Rules

Freitag, 4. Oktober 2013

Excel generieren leicht gemacht

Wer schon einmal Apache POI zur Generierung von Excel Dokumenten verwendet hat, kennt wahrscheinlich Code, der so ähnlich aussieht wie der Folgende:

public class PoiExporter {
public void exportPoi(ShoppingCart shoppingCart, String destFile) throws ExcelGenerationException {
Workbook wb = new HSSFWorkbook();
Sheet sheet = wb.createSheet();
int rownumber = 0;
Row row = sheet.createRow(rownumber);
Cell cell = row.createCell(0);
cell.setCellValue(shoppingCart.getName());
rownumber = 2;
for(ArticleGroup group : shoppingCart.getArticleGroups()) {
row = sheet.createRow(rownumber);
cell = row.createCell(0);
cell.setCellValue(group.getName());
rownumber += 2;
for(Article article : group.getArticles()) {
row = sheet.createRow(rownumber++);
cell = row.createCell(0);
cell.setCellValue(article.getName());
cell = row.createCell(2);
cell.setCellValue(article.getPrice());
}
rownumber += 3;
}
try(FileOutputStream out = new FileOutputStream(destFile)) {
wb.write(out);
} catch(IOException e) {
throw new ExcelGenerationException(e);
}
}
}

Man muss also relativ viel Code schreiben um ein sehr simples Dokument zu generieren. Auch die Wartbarkeit ist nicht ganz so dolle. Jede noch so kleine Änderung sorgt dafür, dass der Code angepasst werden muss. Wenn dann noch viel mit CellStyles gearbeitet wird, hört es irgendwann ganz auf.


Vor einiger Zeit wurde mir von dem Framework jXLS erzählt, mit dem sich Excel Dokumente deutlich einfacher generieren lassen.
Javaseitig ist für das gleiche Beispiel wie oben nur noch folgender Code notwendig:

public class JxlsExporter {
public void exportJxls(ShoppingCart shoppingCart, String template, String destFile) throws ExcelGenerationException {
Map<String, Object> map = new HashMap<>();
map.put("cart", shoppingCart);
XLSTransformer transformer = new XLSTransformer();
try {
transformer.transformXLS(template, map, destFile);
} catch(ParsePropertyException | InvalidFormatException | IOException e) {
throw new ExcelGenerationException(e);
}
}
}

Zusätlich wird noch ein Template benötigt. Das Template ist einfach eine Excel Datei, im Beispiel shopping-list.xls. Dem Template wird eine Map übergeben. In der Map befinden sich die Objekte an denen die Informationen abgefragt werden können, die in dem Excel Dokument dargestellt werden sollen.
Das Template sieht folgendermaßen aus:



Über Expression Language kann nun auf alle Objekte zugegriffen werden, die in der Map gespeichert und an das Template übergeben wurden. Es ist möglich über Listen zu itererieren und Informationen ein- bzw. auszublenden. Hintergrundfarben, Schriftarten, -größen etc. können im Template gepflegt werden und werden dann in das generierte Dokument übernommen.
So lange sich die darzustellenden Informationen nicht ändern, sondern nur die Art und Weise wie die Daten angezeigt werden sollen, muss nur das Template angepasst werden.
Das fertige Dokument sieht das folgendermaßen aus:


Weitere Informationen findet ihr hier: http://jxls.sourceforge.net/

Der Beispielcode befindet sich hier: https://github.com/gossie/jxls-demo