Testgetriebene Entwicklung mit JUnit, EasyMock und Spring

Veröffentlicht in: Java, test-driven development | 0

Die testgetriebene Entwicklung – test-driven development (TDD) – wird häufig in der agilen Softwareentwicklung eingesetzt. Hierbei werden zunächst die Testfälle konzeptioniert und erst anschließend die gewünschte Funktionalität umgesetzt. Aufgrund fehlender Umsetzung werden daher die Testfälle zunächst zwangsläufig fehlschlagen. Erst mit sukzessiver Funktions-Implementierung werden die Testfälle nach und nach erfolgreich durchlaufen.

Am einfachsten zu Testen sind Methoden, die für unterschiedliche Übergabe-Parameter entsprechende Rückgabe-Werte liefern wie z.B. statische Utility-Methoden. Um diese Methoden zu testen wird JUnit verwendet, welches unter http://www.junit.org/ in der aktuellen Version 4.10 zum Download bereit liegt. Alternativ kann man sich JUnit auch als Maven Dependency laden:

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>

Das Testen einer Utility-Methode mit JUnit sieht dann folgendermaßen aus:

assertTrue(MyUtility.returnTrue(„parameter“));

Sofern die zu testende Methode Aufgaben an weitere Klassen delegiert, ist es nicht mehr möglich ausschließlich JUnit Tests zu verwenden, ohne eine vollständige Anwendungs-Umgebung zu initialisieren. Stattdessen müssen zusätzlich alle verwendeten Klassen mit initialisiert und getestet werden.

Hierbei handelt es sich dann nicht mehr um Modultests, sondern um Integrationstests, die definitiv wichtig sind und ihre Berechtigung haben, bei der testgetriebenen Entwicklung aber nicht im Vordergrund stehen. Hier soll nur das zu entwickelnde Modul getestet werden.

Daher werden bei der testgetriebenen Entwicklung Mocking-Frameworks verwendet, damit alle Klassen die in der zu testenden Methode verwendet werden, durch sogenannte Mock-Objekte ersetzt werden können. Diese Mocks bieten die Möglichkeit zu testen, ob die erwarteten Methode-Aufrufe mit den definierten Übergabe-Parametern erfolgen. Findet der Aufruf nicht statt oder ein nicht erwarteter Aufruf, so schlägt der Testfall fehl. Die Funktionalität der gemockten Objekte wird also nicht mit getestet, sondern kann in separaten Testfällen geprüft werden.

Als Mocking-Framework wird im folgenden Beispiel EasyMock verwendet, welches unter http://easymock.org/ in der aktuellen Version 3.1 verfügbar ist. Auch EasyMock ist als Maven Dependency erhältlich:

<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>3.1</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymockclassextension</artifactId>
<version>3.1</version>
<scope>test</scope>
</dependency>

Die erste Library wird zum Mocken von Interfaces benötigt, was als best practice anzusehen ist. Ist es dennoch mal nötig eine Klasse zu mocken, weil man z.B. bestehenden Code nachträglich testen möchte, kann die EasyMock-Classextension verwendet werden.

Kommen wir nun zu einem kleinen Beispiel. Es wird ein UserService getestet der einen User erhält und diesen mittels eines UserDAOs speichern soll. Hierbei wird der UserDAO gemockt und es soll überprüft werden, ob die save-Methode des DAOs auch wirklich aufgerufen wird.

Auch wenn bei der testgetriebenen Entwicklung zuerst der Testfall erstellt werden sollte, folgt zum besseren Verständnis zunächst der UserService und erst anschließend die Testfälle:

public class UserService {

private UserDAO userDAO;

public User save(User user) {
return userDAO.save(user);
}

public void setUserDAO(UserDAO userDAO) {
this.userDAO = userDAO;
}
}

Zuallererst wird ein Setup initialisiert, in welchem der DAO gemockt und die erwarteten Methoden-Aufrufe definiert werden. Die Annotation @Before besagt hierbei, dass das Setup vor der Ausführung jedes einzelnen Testfalls in dieser Test-Klasse ausgeführt werden soll.

@Before
public void setUp() {
name = „userName“;
user = new User(name);
userDAO = createMock(UserDAO.class);
expect(userDAO.save(user)).andReturn(user); // (1)
replay(userDAO);

userService = new UserService();
userService.setUserDAO(userDAO);
}

Wie bereits erwähnt wird der UserDAO gemockt und die Erwartung ausgesprochen, dass die save-Methode mit dem gegebenen User aufgerufen wird. Bei der expect-Methode handelt es sich um eine statische Methode von EasyMock, die verwendet wird, wenn der erwartete Methode-Aufruf einen Return-Value besitzt. Über die andReturn-Methode kann anschließend der Rückgabe-Wert definiert werden, den das Mock-Objekt zurückgeben soll.

Hätte die save-Methode keinen Rückgabe-Wert, sähe die Stelle (1) folgendermaßen aus:

userDAO.save(user);

Der expect-Aufruf würde also einfach weggelassen. Der replay-Aufruf in der darauf folgenden Zeile besagt, dass keine weiteren Aufrufe auf dem UserDAO-Mock erwartet werden und nun nur noch die Aufrufe folgen, die bei Test-Ausführung getätigt werden.

Nun muss der UserService noch den UserDAO-Mock gesetzt bekommen und wir können zum eigentlich Test kommen. Hierbei sollte man sich nicht wundern, das Setup ist meist deutlich länger als der eigentliche Test.

@Test
public void shouldSaveUser() {
User save = userService.save(user);
verify(userDAO);
assertNotNull(save);
assertEquals(„userName“, save.getName());
}

Hier wird die save-Methode des UserService aufgerufen und anschließend die verify-Methode von EasyMock aufgerufen. Damit wird überprüft ob die zuvor definierten Mock-Aufrufe auch wirklich getätigt werden oder nicht. Anschließend kann der Rückgabe-Wert wie gehabt mit JUnit überprüft werden.

Um EasyMock etwas genauer zu verstehen modifizieren wir nun die Zeile (1) der Setup-Methode:

expect(userDAO.save(new User(„userName“))).andReturn(user);

In der Setup-Methode wird dem UserDAO-Mock also ein anderer User übergeben, als dem UserService im Test, lediglich der Namen ist gleich. Da nur der Name auf Gleichheit überprüft wird, läuft der Testfall wie gehabt durch. Fügen wir nun dem Testfall noch folgende Zeile hinzu:

assertEquals(save, user);

Hierauf hin wird der Testfall fehlschlagen. Grund dafür ist, dass EasyMock die equals-Methode der User-Klasse überprüft, um zu Testen ob der Übergabe-Parameter gleich ist. Sofern die equals-Methode nicht überschrieben wurde, wird die equals-Methode von Object verwendet, die auf gleiche Referenz testet. Da hier nun zwei unterschiedliche Objekte mit lediglich den gleichen Property-Werten verwendet werden, schlägt der Testfall fehl. Sobald allerdings die equals-Methode von der User-Klasse überschrieben wird und hierbei auf Namens-Gleichheit getestet wird, ist der Test-Durchlauf wieder erfolgreich.

Dies Verhalten ist auch der Grund, warum ich auf einen weiteren wichtigen Bestandteil von EasyMock kommen möchte, das Capture.

Stellen wir uns vor, der UserService reicht das User-Objekt nicht direkt zum UserDAO durch, sondern erstellt aufgrund des Users ein UserEntity, welches vom UserDAO konsumiert wird. Die Properties des Users sind hierbei identisch mit denen des UserEntities. Die Implementierung sieht also so aus:

public User save(User user) {
UserEntity userEntity = new UserEntity(user.getName());
UserEntity entity = userDAO.save(userEntity);
User result = new User(entity.getName());
return result;
}

Sofern das UserEntity viele Properties beinhaltet, müßte für den Testfall also ein exakt gleiches UserEntity erstellt werden, damit das UserEntity im Testfall und das im UserService erzeugte von EasyMock als gleich anerkannt wird. Sofern die equals-Methode nicht überschrieben werden dürfte, würde der Testfall sogar immer fehlschlagen, da es sich zwangsläufig um zwei unterschiedliche Referenzen handeln würde. Aber auch so ein Fall lässt sich problemlos mit EasyMock abbilden.

Hierzu muss lediglich der übergebene User durch ein Capture ersetzt werden:

captureUser = new Capture<UserEntity>();
userDAO = createMock(UserDAO.class);
expect(userDAO.save(capture(captureUser))).andReturn(userEntity);
replay(userDAO);

Es wird ein Capture Objekt erzeugt, welches nicht direkt in die save-Methode des UserDAOs gegeben wird. Stattdessen wird hier die EasyMock-Methode capture aufgerufen. Nun kann im Test über das Capture auf das dem Mock übergebene Objekt zugegriffen und die Werte überprüft werden:

assertNotNull(captureUser.getValue());
UserEntity userEntity = captureUser.getValue();
assertEquals(„userName“, userEntity.getName());

Mit diesem Funktionsumfang von EasyMock lassen sich bereits die meisten Test-Szenarien abdecken. Ab und an wird man in spezial Fällen Lösungen in der EasyMock-Dokumentation suchen müssen, für die meisten Anwendungsfälle dürfte es aber genügen.

Nun noch ein kleiner Exkurs über die Modultest hinaus zu den Integrationstests. Sofern man Spring verwendet kann es sehr nützlich sein, für Testfälle einen Spring-Context hochzuziehen. Folgende Spring-Library ermöglicht dies:

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>3.0.6.RELEASE</version>
<scope>test</scope>
</dependency>

Nun legt man den zu testenden Service an und definiert diesen in der Spring-XML Konfiguration, welche sich im Classpath befinden sollte:

<version=“1.0″ encoding=“UTF-8″?>
<beans>
<context:annotation-config/>
<bean id=“myService“ class=“MyService/>
</beans>

Nun kann man die XML-Konfiguration über die Annotation ContextConfiguration angeben und muss anschließend die Runner Klasse definieren. Da in der Spring-Konfiguration Annotations aktiviert wurden, kann nun der Service direkt in den Testfall per Autowire-Annotation injected werden:

@ContextConfiguration(locations = { „/test.xml“ })
@RunWith(SpringJUnit4ClassRunner.class)
public class SpringContextTest {

@Autowired
private MyService myService;

@Test
public void shouldExecuteMyService() {
assertNotNull(myService);
assertTrue(myService.doWhatYouHaveTodo());
}
}

Über die testgetriebene Entwicklung wird immer wieder gesagt, dass sie zu langsam und damit zu teuer ist. Dies konnte ich bislang nicht feststellen. Sicherlich ist es zunächst mehr Aufwand testgetrieben zu Entwickeln, aber die Einarbeitung in jedes neue Framework kostet zunächst einmal Zeit und bringt es mit sich, sich bei neuen Gegebenheiten erneut mit der API und deren Dokumentation zu beschäftigen. Der Zeitaufwand der beim Erstellen der Testfälle selber benötigt wird, ist überschaubar und wird durch die direkt bemerkten Flüchtigkeitsfehler wieder wett gemacht. Es muss nicht umständlich ein Server gestartet werden, nur um dann doch wieder ein Fehlverhalten festzustellen. Es ist grundsätzlich schneller einen Testfall auszuführen, als bei einer Software manuell einen ganz bestimmten Zustand zu testen.

OpenOffice-Dokumente mit NOA Verarbeiten

Veröffentlicht in: Java | 0

Der folgende Artikel widmet sich der automatisierten Verarbeitung von OpenOffice Dokumenten. So kann mit Hilfe des Frameworks Nice Office Access (NOA) recht einfach ein OpenOffice Template eingelesen, definierte Platzhalter ersetzt und das Dokument als PDF gespeichert werden.

Die NOA-Library ist unter dem Link http://ubion.ion.ag/solutions/004niceofficeaccess in der aktuellen Version 2.2.3 erhältlich und beinhalten zusätzlich zu NOA auch die benötigten OpenOffice Jar-Archive.

Bevor es zur eigentlichen Entwicklung geht, wird noch ein OpenOffice Template mit OpenOffice-Writer erstellt. Neben selbst zu definierendem Text, muss das Dokument mindestens einen Platzhalter erhalten, der von der Java Anwendung ersetzt werden soll. Zu den hierfür benötigten Feldbefehle-Editor gelangt man über die Menüpunkte Einfügen, Feldbefehl, Andere. Unter Variablen kann man nun eine neue Variable setzten. Hierbei muss sowohl ein Name, als auch ein Wert gesetzt werden. Als Format ist Text auszuwählen.

Um die OpenOffice Verarbeitung nutzen zu können, muss zunächst eine Connection zum OpenOffice-Service hergestellt werden. Im folgenden Beispiel befindet sich dieser auf dem gleichen Rechner wie die Java Anwendung. Sofern für die OpenOffice Installation der Standard-Installationspfad genommen wurde, erhält die Konstante OO_PATH im folgenden Code-Fragment den Wert: „C:Program Files (x86)OpenOffice.org 3“:

Map configuration = new HashMap();
configuration.put(IOfficeApplication.APPLICATION_HOME_KEY, OO_PATH);
configuration.put(IOfficeApplication.APPLICATION_TYPE_KEY, IOfficeApplication.LOCAL_APPLICATION);
IOfficeApplication officeAplication = OfficeApplicationRuntime.getApplication(configuration);
officeAplication.setConfiguration(configuration);
officeAplication.activate();

Im nächsten Schritt wird das anfangs erstellte Template mit dem NOA eigenen DocumentService geladen, bei der Konstante TEMPLATE handelt es sich um den Dateinamen inklusive vollständigem Pfad:

IDocumentService documentService = officeAplication.getDocumentService();
IDocument document = documentService.loadDocument(TEMPLATE);

Nun kommt der wirklich interessante Teil, in dem die Platzhalter des Templates ersetzt werden. Über den TextFieldService erhält man anhand der Methode getUserFields die im Template definierten Platzhalter. Über diese kann nun iteriert werden und über das PropertySet des jeweiligen TextFields auf das zugehörige Property Content, der Text des Platzhalters beliebig gesetzt werden. An den Namen des Platzhalters kann man über die Methode getDisplayText gelangen.

ITextFieldService textFieldService = ((ITextDocument) document).getTextFieldService();
ITextField[] placeholders = textFieldService.getUserTextFields();
for (ITextField field : placeholders) {
XPropertySet propertySet = (XPropertySet)     UnoRuntime.queryInterface(XPropertySet.class, field.getXTextContent());
String placeholder= placeholders.get(field.getDisplayText());
if („myplaceholder“.equals(placeholder)) {
propertySet.setPropertyValue(„Content“, „Platzhalter ist ersetzt“);
}
}
textFieldService.refresh();

Um die Änderungen im Dokument wirksam zu machen, muss der TextFieldService noch aktualisiert werden.
Ein wichtiger Bestandteil der OpenOffice-API ist der Zugriff auf Interfaces, für den die UnoRuntime zur Verfügung steht. Hier wird die Methode queryInterface aufgerufen, deren Übergabe-Parameter zum einen das gewünschte Interface ist – Interfaces der OpenOffice API beginnen immer mit X – zum anderen das Objekt, welches das Interface implementiert. Sofern das Objekt das Interface gar nicht implementiert, wird hier keine Exception geworfen sondern null zurückgegeben, andernfalls kann man den Return-Value auf das erwartete Interface casten und hat somit Zugriff auf die gewünschten Methoden.

XPropertySet propertySet = (XPropertySet) UnoRuntime.queryInterface(XPropertySet.class, field.getXTextContent());

Will man nun das neue Dokument als PDF speichern, wird ein PDFFilter und ein OutputStream benötigt. Der PerstistanceService, der über das NOA-Document verfügbar ist, kann nun mittels der export-Methode den Dokumenten-Inhalt in den übergebenen OutputStream schreiben. Dieser wird anschließend in eine Datei gespeichert und nach dem schließen der Input- und OutputStreams findet man das gewünschte PDF an an der Stelle, die in der Konstanten DOCUMENT definiert ist.

ByteArrayOutputStream out = new ByteArrayOutputStream();
PDFFilter pdfFilter = new PDFFilter();
document.getPersistenceService().export(out, pdfFilter);

byte[] byteArray = out.toByteArray();
ByteArrayInputStream inputStream = new ByteArrayInputStream(byteArray);

FileOutputStream outputStream = new FileOutputStream(new File(DOCUMENT));
IOUtils.copy(inputStream, outputStream);
IOUtils.closeQuietly(inputStream);
IOUtils.closeQuietly(outputStream);

Sobald das PDF generiert wurde, kann das OpenOffice Dokument geschlossen und die OpenOffice Connection deaktiviert werden.

document.close();
officeAplication.deactivate();

Die Verwendung von NOA erleichtert den Zugriff auf OpenOffice enorm, da man sich viele Zwischenschritte sparen kann. Ganz ohne Kenntnisse der OpenOffice-API kommt man allerdings nicht aus. Den dazugehörigen Development Guid findet man unter folgendem Link: http://wiki.services.openoffice.org/wiki/Documentation/DevGuide

Der Event Admin Service

Veröffentlicht in: Eclipse, Java, RCP | 0

Bei der Entwicklung von Software-Architekturen bekommt die Verwendung von Events und EventHandlern eine immer größere Bedeutung. Betrachtet man CQRS – Axon Framework – oder die CDI Events von JEE 6, so ist hier in letzter Zeit ein klarer Trend zu erkennen.

Umso erstaunlicher, dass das Konzept der Events schon seit Oktober 2005 im OSGI Standart – OSGI Service Compendium – existiert. So wird es vom Event Admin Service ermöglicht, Events zu versenden. Diese werden von EventHandlern, die als OSGI Service registriert seien müssen, behandelt. Da es sich bei dem Event Admin Service um einen OSGI Service handelt, kann dieser z.B. über die ServiceRegistry abgefragt werden:

ServiceReference serviceReference = context.getServiceReference(EventAdmin.class.getName());
EventAdmin eventAdmin = (EventAdmin) context.getService(serviceReference);

Um nun ein Event zu versenden, wird wie bei JMS (Java Message Service) ein Topic definiert, aufgrundlage dessen sich entscheidet, welche EventHandler auf das Event reagieren. Im Vergleich hierzu haben sich die CDI- und CQRS-Events deutlich weiterentwickelt, da es sich bei diesen um POJOs handelt, die bestimmen welche EventHandler zuständig sind. Die CDI-Events können zusätzlich noch Annotations als Qualifier verwendet werden, falls zu einem Event verschiedene Handler existieren, die aber nicht alle ausgeführt werden sollen. Sowohl die CDI- als auch die CQRS-Events beinhalten die Informationen, die vom Handler zur weiteren Verarbeitung benötigt werden. Beim Event Admin Service muss stattdessen ein Dictonary erstellt werden, welches diese Informationen in Form einer Map beinhaltet:

Dictionary eventProperties = new Hashtable();
eventProperties.put(„message“, „Hello World“);
Event event = new Event(„my/topic“, eventProperties);
eventAdmin.sendEvent(event);

Der EventHandler selbst muss das gleichnamige Interface implementieren und als OSGI Service mit dem entsprechenden Topic registriert werden. Hier die recht simple Implementierung des EventHandlers:

public class MyEventHandler implements EventHandler {
@Override
public void handleEvent(Event event) {
// handle the event
}
}

Folgendermassen wird nun der EventHandler als OSGI-Service registriert:

Dictionaryproperties = new Hashtable();
properties.put(EventConstants.EVENT_TOPIC, „my/topic“);
context.registerService(MyEventHandler.class.getName(), new MyEventHandler(), properties);

Um den Event Admin Service verwenden zu können, werden folgende Bundles benötigt:

  • org.eclipse.osgi
  • org.eclipse.osgi.services
  • org.eclipse.equinox.event
  • org.eclipse.equinox.ds

Events sind ein sehr gutes Mittel um Softwarearchitekturen zu entkoppeln. So ist es recht verwunderlich, dass es so lange gedauert hat, bis sich dieses Architektur-Pattern auch in anderen Frameworks wiedergefunden hat.

CDI Events – Das Observer Pattern in Java EE 6

Veröffentlicht in: Java, JEE 6 | 2

CDI Events können als ein neues Architekturmuster für Java EE 6 Anwendungen gesehen werden, dass am meisten dem Observer Pattern ähnelt. Auch hier gibt es ein zu überwachendes Objekt, auf welches verschiedenste Komponenten reagieren, deren Implementierung aber unbekannt bleiben soll.

Um dies zu ermöglichen benötigt das zu überwachende Objekt Möglichkeiten zum Registrieren und Entfernen von sogenannten Observern, welche bei bestimmten Änderungen des Objektes benachrichtigt werden.

Bei CDI Events reichen einfache Annotations aus, um die Observer im CDI Kontext zu registrieren. Eine Registrierung beim überwachten Objekt selbst, ist nicht mehr nötig.

Dieses Objekt, bzw. Event ist ein ganz normales POJO mit beliebigen Attributen und entsprechenden getter– und setter Methoden.

public class UserEvent {

private String username;

public UserEvent() {}

public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}

Um das Event zu feuern, auf welches anschließend von den Observern reagiert werden soll, ist folgendes Code Fragment nötig:

 @Inject
private Event<UserEvent> event;

public void doSomething() {
event.fire(new UserEvent(„Mustermann“));
}

Das Registrieren von Observern im CDI Kontext erfolgt mittels der Annotation @Observes und dem Event als Übergabe-Parameter. Der Observer kann nun seine Aufgabe verrichten und dabei auf das Event zugreifen. Die Implementierung eines Interfaces ist nicht mehr nötig:

public class UserHandler {
public void handleUser(@Observes UserEvent event) {
// do what you have to do …
}
}

Soll die Abarbeitung innerhalb eines Observers asynchron erfolgen, d.h. nach dem Aufruf der fire-Methode des Events nicht auf den Observer gewartet werden, kann dies mit der Annotation @Asynchronous innerhalb des Observers definiert werden:

@Stateless
public class UserHandler {

@Asynchronous
public void handleUser(@Observes UserEvent event) {
// do what you have to do … but asynchronously
}
}

Mit Hilfe von CDI Events kann man die Software-Architektur recht einfach entkoppeln und hat eine Möglichkeit geschaffen, leicht Funktions-Erweiterungen implementieren zu können.

Bereits vor CDI gab es Frameworks, die Events unterstützt haben. Hier sei das Axon Framework (CQRS – Command Query Responsibility Segregation) genannt, wobei hier das Event-, bzw. Command-Handling nur eine Komponente von vielen war.

Ganz neu ist die Event Idee auf jeden Fall nicht, nun hat sie aber ihren Weg in den Java EE Standard gefunden.