Organisation von Unittests

Innerhalb eines komplexen Projektumfeldes ist der Einsatz von Unittests besonderes hohen Anforderungen hinsichtlich der Qualität der Testorganisation unterworfen. Besonders die Trennung von Programm- und Testcode als auch die exakte Vermessung der Software hinsichtlich der Anforderungen mittels JUnit und anderer Testframeworks im Umfeld von JUnit stellt immer wieder eine Herausforderung für Entwickler und technische Projektleiter dar.

Häufige Anforderungen an die Testorganisation sind:

  1. Testcode soll vom Programmcode getrennt sein.
  2. Logische und konkrete Testfälle sollen voneinander getrennt sein.
  3. Für den Test von Interfaces sollen abstrakte Testvorgehensbeschreibungen geliefert werden.
  4. Testcode soll einfach paketiert werden können.
  5. Testframeworks sollen sich sowohl integrieren als auch herauslösen lassen.
  6. Zur Vermessung unterschiedlicher Anforderung muss der Einsatz unterschiedlicher Testarten berücksichtigt werden.
  7. Die Vermessung unterschiedlicher Anforderungen erfordert die Ausführung fachlich als auch technisch unterschiedlich motivierter Testssets.
  8. Die Vermessung unterschiedlicher Anforderungen ist zeitlich und organisatorisch über Teststufen in den Softwareentwicklungsprozess zu integrieren.

Wie lassen sich die einzelnen Punkte realisieren?

1. Testcode soll vom Programmcode getrennt sein, um einerseits Hintertüren für Schadsoftware zu vermeiden und andererseits die Kernlogik des Programms sauber zu halten. Damit wird die Wartung von Programmcode als auch von Testcode vereinfacht, weil beide Aspekte getrennt behandelt werden. Wie trennt man den Code sinnvollerweise? Generiert man beispielsweise mit Maven ein Java-Projekt, werden standardmäßig zwei Source-Code-Folder angelegt, das sind src/main/java und src/test/java. Paketiert man seine Software auch mit Maven, sollte man aus beiden Foldern auch zwei unterschiedliche Pakete erstellen; Eines für den Produktivcode, eines für die Tests. Überlicherweise trägt das JAR mit den Tests den Classifier -test als Namenssuffix. Empfehlenswert ist es, in beiden Foldern die gleiche bzw. eine ähnliche Paketstruktur zu wählen, sodass man zu einem Paket oder einer Klasse die passenden Tests leicht wieder findet.

Einer Klasse com.company.project.package.subpackage.MyClass sollten dann entsprechende Tests im Package com.company.project.package.subpackage gegenüberstehen. (siehe auch Punkt 4).

2. Logische Testfälle sind Testfälle, die keine Angaben zu konkreten Eingabedaten enthalten. Sie gehen meist aus Äquivalenzklassenanalysen hervor und legen nur Eingabebereiche fest, z. B. Werte größer 0, Werte bis 18, Werte größer 18. Mit einer anschließenden Grenzwertanalyse können aus den Äquivalenzklassen konkrete Eingabewerte ermittelt werden und konkrete Testfälle erstellt werden.

Die Testfälle sollten natürlich separiert werden. Logische Testfälle – und folglich abstrakte Testfälle – bename ich deshalb mit TestCase. Sie geben den allgemeinen Testrahmen vor. (siehe auch Punkt 3 und 4)

Stellen wir uns ein füllbares Objekt vor. Füllbare Objekte können leer, voll oder nur zum Teil gefüllt sein. Daraus ergeben sich drei gültige Äquivalenzklassen. Außerdem gibt es eine ungültige Äquivalenzklasse, und zwar wenn der Füllstand negativ ist.
Beispiele für Tests mit Zusammenfassung der Testmethoden sind:

public abstract class EmptyFillableTestCase {
	testToFillWithNegativeValue()
	testToFillWithZeroValue()
	testToFillWithPositiveValue()
	abstract createEmptyFillable()
}

public abstract class FullFillableTestCase {
	testToFillWithNegativeValue()
	testToFillWithZeroValue()
	testToFillWithPositiveValue()
	abstract createFullFillable()
}

public abstract class HalfFilledFillableTestCase {
	testToFillWithNegativeValue()
	testToFillWithZeroValue()
	testToFillWithPositiveValue()
	abstract createHalfFilledFillable()
}

public abstract class NegativeFilledFillableTestCase {
	testToFillWithNegativeValue()
	testToFillWithZeroValue()
	testToFillWithPositiveValue()
	abstract createNegativeFilledFillable()
}

3. Wie bereits in einem meiner vorherigen Posting beschrieben, sollten auch Interfaces mit entsprechenden Testfällen ausgestattet sein, sodass im Sinne des spezifikationsorientierten Testens unkompliziert passende Tests entwickelt werden können. Hier ist die empfohlene Vorgehensweise nochmals kurz zusammengefasst:
Zunächst ist die Spezifikation für das Interface zu erstellen: z. B. „Die Methode findNextPage() eines Interfaces Pageable liefert für frisch erzeugte Objekte immer null.“ Anschließend ist aus der Spezifikation für das Interface ein abstrakter Testfall zu erstellen: Sagt die Spezifikation wie im Beispiel für die Methode findNextPage() des Interfaces Pagable, dass für frisch erzeugte Objekte immer null geliefert wird, würden wir folgenden abstrakten Testfall mit der zugehörigen Factorymethode erstellen:

public interface PageableTestCase {
	testFindNextPageIsNull()
	createPageableUnderTest()
}

Die Tests für die implementierenden Klassen erstellt dann der Entwickler, in unserem Fall könnten das folgende Tests sein:

public class DocumentPageableTest { ... }
public class MemoryPageableTest { ... }

Weil spezifikationsorientierte bzw. abstrakte Tests einen bestimmten Aspekt einer Anwendung oder eines Frameworks testen und mit dem Kern der Anwendung oder des Frameworks nichts zu tun haben, sollten diese Tests in einem eigenen Package platziert werden. Deswegen ist es sinnvoll die abstrakten Tests in einem Subpackage test abzulegen. Gibt es zum Interface com.company.framework.MyInterface abstrakte Tests, dann sollten diese im Package com.company.framework.test abgelegt werden, z. B. als com.company.framework.test.MyInterfaceTestCase platziert werden. Durch geschickte Differenzierung bei den Namespaces für die Entwicklungsartefakte lässt sich das Interface sehr leicht in das JAR com.company.framework.jar und die abstrakten Testfälle in das JAR com.company.framework.test.jar assemblieren. Wichtig ist jedoch die Benamung der Artefakte, die häufig zu Verwechslungen führt: Während das Archiv com.company.framework-tests.jar die konkreten Tests des eigentlichen Frameworks enthält, wird mit dem Subpackage test ein eigener Namespace für abstrakte Tests bzw. logische Testfälle aufgespannt, die aus dem jeweiligen Framework hervorgehen bzw. beigelegt sind. In der Regel wird das besonders bei Frameworks der Fall sein, denen für alle Interfaces passende Tests beigelegt werden, um den benutzenden Entwicklern Tests gegen die Interface-Spezifikation zu ermöglichen.

4. Durch die Benamung abstrakter Tests mit dem Suffix TestCase und die Verwendung des Suffix Test für alle konkreten Tests, wird die Paketierung wesentlich vereinfacht. Durch einfaches Pattern-Matching auf die beiden Suffixe vereinfacht sich die Paketierung erheblich.

5. Durch die bereits oben beschriebene Trennung von abstrakten und konkreten Testfällen als auch die Platzierung der Tests innerhalb passender Packages und Subpackages, lassen sich Testframeworks ähnlich leicht herauslösen wie es auch beim sonst üblichen Refactoring möglich ist. Durch die darüberhinaus exakte Trennung des jeweiligen Codes werden Access Violations durch Klassen, die als „package protected“ oder „protected“ deklariert sind, weitestgehend vermieden.

6. Beim Test einer Anwendung spielen neben rein funktionalen Anforderung natürlich immer eine ganze Menge weiterer Anforderungen eine Rolle, die häufigsten betreffen die Performance, die Usability und die Sicherheit. Diese Anforderungen sind bei der Testentwicklung ebenso zu berücksichtigen und es sind geeignete Tests daraus zu entwickeln. Sofern man die zu testenden Klassen sorgfältig benamt hat, sollte auch die Benamung der Testfälle kein Problem darstellen.

Beispiel: Für ein Interface RemoteService und die entsprechende Implementierung UserRemoteService könnten Performance-Anforderungen in einen abstrakten ResponseTestCase resultieren, sodass sich folgende Klassen ergeben würden:

com.company.remoteframework.RemoteService
com.company.remoteframework.test.RemoteServiceResponseTestCase
com.company.user.UserRemoteService
com.company.user.UserRemoteServiceTest
com.company.user.UserRemoteServiceResponseTest

Benamt man die Testfälle und Tests sorgfältig und platziert sie wie bereits oben angedeutet in den passenden Packages, können auf diese Weise neben funktionalen Anforderungen auch nicht-funktionale abstrakte Tests differenziert in einzelnen JAR-Archiven ausgeliefert werden.

7. Hat man entsprechend der fachlichen Domain und des technischen Aufbaus der Anwendung die Tests passend benamt, kann man entsprechend der fachlichen und funktionalen Ausrichtung unterschiedliche Test-Subsets bilden. Besteht die fachliche Domain beispielsweise aus Employees, FormerEmployees und Workspaces einerseits, die technische Architektur aus Services, Repositories und Domain-Objekten andererseits, könnten aus den funktionalen und nicht-funktionalen Anforderungen folgende Tests abgeleitet werden:

EmployeeServiceRemotePerformanceTest
EmployeeServiceInternalPerformanceTest
EmployeeServiceSecurityTest
EmployeeRepositorySecurityTest
EmployeeRepositoryTest
EmployeeTest

FormerEmployeeServiceRemotePerformanceTest
FormerEmployeeTest

WorkspaceTest
WorkspaceServiceRemotePerfomrmanceTest
WorkspaceRepositoryTest
WorkspaceRepositorySecurityTest

Je nach Teststufe und Testanforderung, könnten nun unterschiedliche Test-Subsets geschnitten werden und per Pattern-Matching durch den Testrunner selektiert und ausgeführt werden, z. B.:

  • Test über die Teildomain FormerEmployee: FormerEmployee*Test würde alle Tests in Zusammenhang mit FormerEmployee ausführen.
  • Test über die Gesamtdomain Employee: *Employee*Test würde alle Tests in Zusammenhang mit Employee ausführen, und zwar auch jene, die mit FormerEmployee in Zusammenhang stehen.
  • Test aller Repositories: *RepositoryTest
  • Test aller Security-Anforderungen in Zusammenhang mit Employee: *Employee*SecurityTest
  • Test aller Performance-Anforderungen in Zusammenhang mit Employee-Services: *EmployeeService*PerformanceTest

8. Um die Testaktivitäten bei der Entwicklung komplexer Softwaresysteme besser organisieren zu können, werden die einzelnen Testaktivitäten überlicherweise auf einzelne Teststufen verteilt. Dabei werden entsprechend des Entwicklungsstandes der Software unterschiedliche Testaktivitäten durchgeführt. Weil mit Unittest-Frameworks wie JUnit nicht nur reine Unittests automatisiert werden können, sondern wie oben gezeigt auch andere Testarten realisiert werden können, stellt sich die Frage, wie unter Berücksichtigung dieser Teststufen die konkreten Tests abgelegt werden können. Hat man seine Tests bereits nach Punkt 7 durch eine geeignete Benamung zu passenden Testsets gebündelt, lassen sich diese Testsets sehr einfach in die einzelnen Teststufen integrieren bzw. die Teststufen mit den für den Entwicklungsstand typischen Tests bestücken. Hier ein Beispiel:

  • In der Teststufe Komponententests sollen alle Tests durchgeführt werden, die reine Unittests sind, keine Performancetests sind (z. B. *ResponseTest) bzw. keine Integrationstests (z. B. *DaoTest, *SecurityTest) sind.
  • In der Teststufe Integrationtest wollen wir alle DAOs als auch RemoteServices testen.
  • In der Teststufe Systemintegrationstest wollen wir mit JMeter Performancetests ausführen.
  • In der Teststufe Systemabnahmetest wollen wir alle Selenium-Tests ausführen.
  • Für den Test und die Freigabe eines Release sollen darüber hinaus in der Teststufe Integrationstest alle Unittests ausgeführt werden.

Eine spezielle Ablage der Tests in den Projekten und Sourcefoldern anhand der Teststufen erübrigt sich damit, denn durch die in den Punkten 1 bis 7 erläuterten Maßnahmen ist uns die passende Ablage, Benamung und Paketierung unserer Testfälle und Tests als auch die gezielte Bündelung unserer Testsets bereits perfekt gelungen. Die Teststufen können damit auf einfachste Weise mit den gewünschten Tests bestückt werden. Als weitere Optimierung wäre denkbar, dass werkzeugspezifische Blackbox-Tests wie etwa jene mit Selenium in ein separates Projekt ausgelagert werden, sodass die Testentwickler im Sinn echter Blackbox-Tests ihre Oberflächentests entwickeln können.

Zusammenfassend ist es auf diese Art und Weise gelungen, Tests gezielt und ressourceneffizient in unterschiedlichen Phasen des Entwicklungs- und Build-Prozesses zu integrieren.

Advertisements
Veröffentlicht in Test. Leave a Comment »

Wertesemantik bei Login-IDs

Seit einiger Zeit erlebe ich wieder häufiger, dass sich Administratoren kleiner IT-Umgebungen den Kopf über die Namenskonventionen bei der Vergabe von Login-IDs zerbrechen. Eine besonders beliebte Praxis sind Login-IDs die sich aus Vorname und Nachname des Benutzers bzw. aus Teilen des Namens oder Geburtstags zusammensetzen. Als massgeblicher Vorteil wird ins Feld geführt, dass Benutzer sich ihren Login-Name dann besser merken könnten und damit die Anmeldung vereinfacht wird.

Allerdings ergeben sich damit zahlreiche Probleme:

  • Bei statistisch häufiger auftretenden Namen wie Max Müller hat man recht schnell ein Problem, denn max.mueller darf nicht doppelt vergeben werden. Kurzerhand wird mit max.mueller1, max.mueller2 usw. eine x-beliebige ID ins Spiel gebracht, die das Namenskonzept über den Haufen wirft und für den Benutzer nicht plausibel ist: „Warum hat der eine Max die 1 und ich die 2? Bin ich die Nummer 2 hier im Geschäft?“
  • Besonders hässlich wird die Sache, wenn Max Müller im System A die Login-ID max.mueller2 hat und im System B die Login-ID max.mueller22. „Welche Login-ID brauche ich für was? Warum bin ich hier die 2 und dort die 22?“
  • Noch viel häufiger ist der Fall, dass Benutzer ihre Namen wegen Heirat ändern, dann passt der Name nicht mehr. Und nicht selten bestehen Anwender bei Namensänderung nach Scheidung zu Recht auf die Änderung der Login-ID, denn in vielen Anwendungen wird diese angezeigt, z. B. in Änderungshistorien von Anwendungen. Wird der Name aber dann tatsächlich geändert, ist das ID-Konzept plötzlich dahin. Besonders wenn die ID fest in archivierten Daten und Dokumenten verdrahtet ist, die sich nicht mehr so ohne Weiteres ändern lassen, lässt sich nicht mehr auf den eigentlichen Benutzer schließen, der im Dokument erwähnt wird. Das kann zu einem riesigen Problem werden, wenn nicht mehr klar ist, welcher Anwender dann tatsächlich hinter einer ID steckte, wenn Max Mustermann zwischenzeitlich Max Müller heißt und ein zweiter Max Mustermann ins Unternehmen einsteigt.
  • Es ließen sich noch mehr Nachteile aufführen, die alle den grundsätzlichen Charakter von IDs und deren Verknüpfung zur Entity – in unserem Fall zur Entity „Person“ – betreffen.

Bewährt hat sich bei Login-IDs folgendes:

  • Verwenden Sie grundsätzlich keine Namenskonvention, sondern eine x-beliebige ID. Nummerieren Sie beispielsweise alle Benutzerkonten stur durch oder nehmen Sie einen generierten String aus Buchstaben und Zahlen. Jeder Anwender, der seine ID mehrere Wochen auf seinem Rechner eingetippert hat, kann sie im Schlaf aufsagen wie seinen Geburtstag.
  • Verwenden Sie eine möglichst kurze ID. Viele Systeme beschränken die Login-ID auf eine bestimmte Länge. Anwender, die nicht mit 10-Fingern schreiben, werden es Ihnen danken.
  • Verwenden Sie aus dem selben Grund für alle IDs immer die gleiche Länge.

Nun gibt es das obige Problem, dass der Anwender schwer zwischen den Benutzerkonten aus unterschiedlichen Benutzerdatenbanken unterscheiden kann, wenn es in der IT-Umgebung mehrere Benutzerdatenbanken gibt. Trotz Active Directory, LDAP und SSO ist es nicht immer möglich, ein und dieselbe Benutzer-ID in allen Systemen zu verwenden und dann kann Wertesemantik durchaus Sinn machen. (Ich spreche ausdrücklich von „kann Sinn machen“, denn am Ende werden wir sehen, dass auch diese Semantik zu den gleichen Problemen führt).

Um das Problem von max.mueller2 und max.mueller22 in Systemumgebung A bzw. Systemumgebung B zu umschiffen, kann man dem Benutzer etwas Wertsemantik auf den Weg geben. Sinnvoll ist es, dem Anwender einen Präfix als Hilfe zu geben z. B. AD für Benutzerkonten aus dem Active Directory oder LD für Benutzerkonten aus dem LDAP oder z. B. S1 für Benutzerkonten aus der Benutzerdatenbank 1.

Damit ergeben sich folgende Login-IDs, die sich prima an den Anwender kommunzieren lassen und die er sich prima merken kann:

  • AD-DIF22X3 für das Active-Directory-Benutzerkonto.
  • LD-DIF22X3 für das LDAP-Benutzerkonto.
  • S1-DIF22X3 für das Benutzerkonto aus Benutzerdatenbank 1.

Vorallem in sehr heterogenen IT-Umgebungen ist das für den Anwender ein Segen, wenn er nicht mit unterschiedlichen technischen Begriffen – wie Active Directory, LDAP, lokales oder Domain-Benutzerkonto – konfrontiert wird, sondern wenn ihm einfach mitgeteilt wird: „Bitte verwenden Sie ihr S1-Konto für die Anmeldung an System X und ihr LD-Konto für die Anmeldung an System Y.“ Das ist für den Benutzer plausibel, genauso wie er in der großen weiten Internet-Welt maxi@spassvoegel.de für die Anmeldung an Chat-Foren verwendet und max.mueller22@gmx.de bei Amazon.

Sie ahnen es aber bereits: Mehr als 10 weitere Benutzerdatenbanken sollten Sie im obigen Fall nicht haben, dann ist der ID-Pool erschöpft. Sieht man aber davon ab, dass selbst in großen IT-Landschaften mehr als 10 Benutzerdatenbanken eher selten sind, kann diese Konvention für Anwender und Administratoren durchaus vorteilhaft sein.

Im Zweifelsfall: Vergessen Sie’s! – IDs mit Wertsemantik machen im wahrsten Sinne des Wortes einfach keinen Sinn.

Veröffentlicht in Sonstiges. Leave a Comment »

Java-Applets und Java-Web-Applications in Multi-Module-Projekten referenzieren

Java-Applets sind mittlerweile recht selten geworden. Dennoch gibt es einige Anwendungsfälle, bei denen ein Applet unter Umständen in eine Web-Applikation eingebunden werden muss. Insbesondere dann, wenn eine Legacy-Anwendung mit Java-Swing umgesetzt wurde und aus mehreren externen Libraries besteht, möchte man die Anwendung nicht zwangsläufig neu erfinden und stattdessen mit einem Applet auf dem kürzesten Weg ins Web bringen.

Kompliziert wird dieses Unterfangen ganz besonders dann, wenn mit Maven gebaut werden soll und die Abhängigkeiten der Anwendungsteile innerhalb großer und komplexer Projekte berücksichtigt werden sollen.

Wir greifen dazu folgendes Beispielprojekt auf, das aus drei Teilen besteht:

de.example.component (Multi-Module-Project)
+---de.example.app (jar - Anwendungscode mit Main bzw. Applet-Klasse)
+---de.example.core (jar - Business-Logik)
\---de.example.web (war - Web-Anwendung)

Die Web-Anwendung bindet auf einer ihrer HTML-Seiten die Applet-Klasse bzw. den Anwendungscode wie üblich folgendermaßen ein:

			<applet code="de.example.app.AppletMain.class" archive="applet/de.example.app.jar,applet/de.example.core.jar,applet/com.external.lib.jar" width="100%" height="100">
			</applet>

Das App-Module „de.example.app“ mit den eigentlichen Appletsourcen verwendet das Core-Module „de.example.core“. Darüberhinaus ist denkbar, dass die einzelnen Module externe Libraries referenzieren, die an den richtigen Stellen in die Anwendung verbaut werden müssen. In unserem Beispiel soll das durch die externe Library „com.external.lib“ veranschaulicht werden.

Das POM der Module „Core“ und „App“ als auch das Multi-Module-POM sollten für den geübten Maven-Anwender leicht umzusetzen sein.

Schwierig wird es beim POM der Web-Anwendung. Weil das Applet vor der Web-Anwendung gebaut werden muss, jedoch vor dem Applet erst das Core-Module zu bauen ist, müssen wir das App-Module so in den Dependency-Tree unseres Multi-Module-Projekts einhängen, dass die Build-Reihenfolge Core—App—Web eingehalten wird.

Dazu definieren wir die folgende Abhängigkeit im POM des Web-Moduls, wobei ${project.version} in unserem Fall durch die Version des Multi-Module-Projekts ersetzt wird:

		<dependency>
			<groupId>de.example</groupId>
			<artifactId>de.example.app</artifactId>
			<version>${project.version}</version>
			<scope>provided</scope>
		</dependency>

Diese Abhängigkeit sorgt allerdings nur dafür, dass unser Applet vor der eigentlichen Web-Anwendung gebaut wird. Leider wird das aus dem Core-Module resultierende JAR nicht in das Web-Archive paketiert. Gleiches gilt auch für die vom Applet benötigten Libraries; auch diese fehlen im WAR, sodass beim Aufruf der HTML-Seite die JVM ins Leere greift und uns mit Fehlermeldungen bestraft.

Eine Änderung des Scope-Attributs würde das Problem übrigens nicht beseitigen. Es ist bewusst so gewählt. Warum wir den Scope ausgerechnet auf „provided“ setzen müssen, werden wir sogleich erfahren.

Die Ursache des Problems liegt nämlich darin verborgen, dass ein Applet nichts anderes als eine einfache HTML-Ressource ist und dementsprechend auch als HTML-Ressource in das WAR eingebunden werden muss. Der von uns verwendete Dependency-Mechanismus berücksichtigt jedoch nur Dependencies, die als Libraries von der Web-Anwendung direkt benötigt und im WEB-INF/lib-Verzeichnis der Web-Anwendung abgelegt werden, auf die lediglich der Web-Container des Servers Zugriff hat, der Web-Browser des Anwenders hingegen jedoch nicht.

Kurz gesagt, die Einbindung der Libraries als Dependencies der eigentlichen Web-Anwendung würde uns nicht weiterhelfen, im Gegenteil, sie ist sogar überflüssig und würde das WEB-INF/lib-Verzeichnis mit überflüssigen Libraries überfüllen. Mit dem Scope „provided“ sorgen wir genau dafür, dass die Libraries nicht über den Dependencies-Mechanismus des Web-Archivs kopiert werden.

Was heißt das im Detail?

Wie für HTML-Ressourcen üblich, muss auch unser Applet inklusive der Libraries „de.example.app.jar“ „de.example.core.jar“ und „com.external.lib.jar“ so im WAR abgelegt werden, dass ein Browser diese Ressourcen vom Server anfordern kann. Prinzipiell bietet sich dazu jeder beliebige Unterordner des WAR an, außer eben WEB-INF und META-INF, wie wir eben festgestellt haben. Wir werden die Libraries dazu im Unterordner „applets“ ablegen. Wie bereits oben im HTML-Ausschnitt zu sehen ist, haben wir das Unterverzeichnis im APPLET-Tag schon verwendet.

Wie bekommen wir die ganzen abhängigen Libraries in das applet-Verzeichnis?

Sicherlich könnte man die Libraries per Hand in das applet-Verzeichnis kopieren. Bei Code-Änderungen müssten wir allerdings die Libraries erneut dorthin kopieren, was spätestens bei größeren Projekten mit vielen externen Libraries sehr viel Aufwand verursachen würde und zudem sehr fehleranfällig wäre.

Die Lösung zum Kopieren abhängiger Libraries liegt im Maven-Dependency-Plugin. Mit diesem Plugin kann man nämlich sämtliche Abhängigkeiten einsammeln und an ein beliebiges Ziel kopieren. Dazu ergänzen wir im POM des Web-Moduls folgende Plugin-Konfiguration:

			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-dependency-plugin</artifactId>
				<version>2.4</version>
				<executions>
					<execution>
						<id>copy-dependencies</id>
						<phase>prepare-package</phase>
						<goals>
							<goal>copy-dependencies</goal>
						</goals>
						<configuration>
							<outputDirectory>${project.build.directory}/${project.build.finalName}/applet</outputDirectory>
							<stripVersion>true</stripVersion>
							<includeScope>compile</includeScope>
							<artifactItems>
								<artifactItem>
									<groupId>de.example</groupId>
									<artifactId>de.example.app</artifactId>
									<type>jar</type>
									<overWrite>true</overWrite>
								</artifactItem>
							</artifactItems>
						</configuration>
					</execution>
				</executions>
			</plugin>

Mit der Konfiguration sorgen wir dafür, dass vor der eigentlichen Paketierung des WARs, und zwar in der Maven-Phase „prepare-package“ das copy-depencies-Goal des Maven-Dependency-Plugins ausgeführt wird (Zeile 8 und 10). Wichtig dabei ist, dass als ID-Attribute „copy-dependencies“ gewählt wird, sodass die default-Execution überschrieben wird (Zeile 7).

Als Output-Directory geben wir das Unterverzeichnis „applet“ des WAR-Buildfolders an (Zeile 13). Mit stripVersion können wir die Version-Suffixe an den JARs beim Kopieren entfernen lassen (Zeile 15), sodass wir das Applet-Tag im HTML nicht korrigieren müssen, wenn sich die Versionsnummer unserer Anwendung oder die einer referenzierten Library irgendwann einmal ändern sollten.

Als <ArtifactItem> muss lediglich unser Projekt mit dem Applet angeben werden. Das Maven-Dependency-Plugin sorgt dafür, dass alle von „de.example.app“ ausgehenden Abhängigkeiten kopiert werden.

Ein Aufruf von mvn clean package auf das Multi-Module-Projekt liefert uns nun endlich ein frisches WAR mit dem von uns gewünschten Inhalt:

de.example.web.jar (Multi-Module-Project)
|   index.html
|
+---applet
|       com.external.lib.jar
|       de.example.app.jar
|       de.example.core.jar
|
+---META-INF
\---WEB-INF

Nach dem Deployment des WARs und dem Aufruf der entsprechenden Web-Seite im Browser sollte sich das Applet präsentieren.

Ein besonderes Schmankerl ist allerdings noch offen…

In Eclipse lassen sich Web-Projekte sehr komfortabel mit dem Kommando „Run As -> Run on Server“ starten. Für einen solchen Klick-and-Run-Test ist die obige Konfiguration allerdings nicht geeignet, weil Eclipse für den Build und das Deployment der Web-Anwendung auf einen Webserver einen anderen Mechanismus verwendet.

Sicherlich könnte man auch hier wieder die benötigten Libraries in den WebContent- oder webapp-Ordner des Projekts kopieren, was aber sehr fehlerträchtig ist und zu Problemen mit dem Maven-Build führen würde.

Viel besser ist es, dass Eclipse-Projekt so zu konfigurieren, dass die Libraries vor dem Start des Servers von Eclipse kopiert werden. Dazu öffnet man die Project-Properties des Projekts „de.example.web“ und passt unter „Deployment Assembly“ die Packaging Structure des „Web“-Projekts an, indem man mit „Add“ folgende Assembly-Mappings hinzufügt:

Add of "Folder"                    src/main/java                    WEB-INF/classes
Add of "Folder"                    src/main/webapp                  /
Add of "Archive from File System"  C:/mvnrepo/com/external/lib[..]  applet/
Add of "Project"                   de.example.core                  applet/de.example.core.jar
Add of "Project"                   de.example.app                   applet/de.example.app.jar

Die beiden letzten Einträge sind insofern notwendig, dass sowohl das Core-Projekt als auch das App-Projekt jeweils als Project Reference in das Web-Projekt eingehangen werden, sodass Eclipse unser Web-Projekt immer mit einem Applet auf Basis des aktuellsten Sourcecode-Standes versorgt und auf dem Test-Server bereitstellt.

Anschließend lässt sich das Web-Projekt auch aus Eclipse auf einen beliebigen Server deployen und sofort im Browser starten.

Use parts of filenames of numbered files to rename files in another directory

Sometimes you want to rename a bunch of files by using parts of the filenames of some other files. A very often case is, that you have a bunch of pictures, where you have added a human readable suffix for better classification.

PIC0001_alice-at-work.JPG
PIC0002_bob-at-home.JPG
PIC0003_harry-in-holidays.JPG
PIC0004_susan-at-shopping.JPG

Unfortunately you also have another folder were you have some corresponding files. For example you have put a copy of the original files into a second folder. A similar case is, that you have the original files in JPG-Format and for publishing you converted the files to PNG.

\copy
  PIC0001.PNG
  PIC0002.PNG
  PIC0003.PNG
  PIC0004.PNG

Now a very common task is to rename all files in the second folder „copy“ in the same way so that „PIC0001.PNG“ becomes „PIC0001-alice-at-work.PNG“ like your Files in the original JPG-Folder. A very anoying task if you have to rename all files by hand. And maybe you already tried to put this task onto the commandline or into a batch script, but you stumbled around the usage of filenames and variables.

Because this isn’t a really trivial task I wrote the following batch script to overcome this renaming problem on Windows machines:

@ECHO OFF
SETLOCAL EnableDelayedExpansion
FOR %%F IN (PIC*.JPG) DO (
	SET filename_src=%%F
	SET number=!filename_src:~3,4!
	FOR %%I IN (*!number!*.*) DO (
		SET filename_out=%%~nI
		ECHO !number!
		ECHO Renaming !filename_out!
		RENAME .\copy\PIC!number!.png !filename_out!.png
	)
)

Put this code into a file named „conveyer.cmd“ and change the corresponding lines for your customization needs. Sure, this code needs some explanation:

Line 2:
This statement causes each variable to be expanded during script execution. Unfortunately all variables are expanded at parse time by default, especially the temporary number of the inner loop. So we need to call „SETLOCAL EnableDelayedExpansion“ to get the number immediately at runtime. Moreover to expand a variable immediately you need to enclose it with an exclamation mark (!).

Line 3:
At this line you can put your own filename pattern, to select all files for renaming.

Line 5:
At this line we extract the number of the file. At the example above the number is found at position 3 and has a length of 4 characters. Keep in mind to enclose the full variable with an exclamation mark.

Line 9:
To finally rename the file you need to customize the filepattern that catches the file to be renamed. In the example above the files to rename reside at the subfolder „copy“ of the files that filenames we are use for renaming.

Finally put this script into the original folder and call „conveyer.cmd“. With some more little changes you can further customize this lines for your special needs. For myself I use this script to get the names of sent tiff-fax-documents and to rename the corresponding compressed pdf-documents for archiving.

Missing Option ab Excel 2007: Excel-Diagramme an Fenstergröße anpassen

Benutzer, die Exceldiagramme häufig auf unterschiedlich großen Bildschirmen betrachten, vermissen in Excel ab Version 2007 häufig die Option „Fenstergröße angepasst“, die es bis Excel 2003 noch gab. Mit dieser Option konnte man sicherstellen, dass die Diagrammfläche dynamisch an die jeweilige Fenstergröße angepasst wurde. Mit der Einführung von Excel 2007 wurde diese Option leider gestrichen, kann aber mit den folgenden Schritten in ähnlicher Form reaktiviert werden.

Öffnen Sie dazu im betreffenden Arbeitsblatt das Ribbon „Ansicht“. Dort wählen Sie „Zoommodus:Auswahl“. Wichtig ist, dass das Arbeitsblatt nun als XLSX abgespeichert wird. Schließen Sie das gespeicherte Arbeitsblatt und öffnen Sie es erneut. Wenn Sie nun die Größe des Fensters verändern, wird die Größe des Diagramms wieder automatisch angepasst.

Bei Excel 2010 ist diese Funktion leider an einer anderen Stelle versteckt. Dort wählt man im Ribbon „Ansicht“ die Schaltfläche „Zoom“. In dem sich öffnenden Dialog wählt man die Option „An Markierung anpassen“ und speichert das Arbeitsblatt. Anschließend passt sich die Größe des Diagramms ebenso automatisch an das Fensters an.

Speichert man das Arbeitsblatt nicht als XLSX sondern als XLS und öffnet das Arbeitsblatt erneut, wird das Diagramm nicht flächenfüllend dargestellt, sodass man wieder scrollen oder zoomen muss.

Veröffentlicht in Sonstiges. Schlagwörter: , . Leave a Comment »

Wicket + Maven + Eclipse: „Can not determine Markup“

Vor einigen Tagen begegnete mir beim Ausprobieren einer Wicket-Applikation folgende Exception:

org.apache.wicket.markup.MarkupNotFoundException: Can not determine Markup. Component is not yet connected to a parent. [Page class = de.shylynx.wicket.playground.pages.HomePage, id = 0, render count = 1]

Das Merkwürdige an diesem Verhalten war, dass die Anwendung bereits einwandfrei lief und scheinbar plötzlich Ihren Dienst versagte. Eine genauere Recherche im Netz führte mich dann jedoch auf eine Spur zur endgültigen Ursache des Problems.

Bekanntermaßen bestehen Wicket-Applikationen immer aus einer WebPage-Definition und einer HTML-Seite, die das Markup enthält, z. B. HomePage.java und HomePage.html. Gewöhnlicherweise werden beide Source-Files immer im gleichen Package des Source-Folders src/main/java abgelegt, sodass man beide Dateien leicht zur Hand hat. Das Tückische daran ist, dass mein Eclipse-Projekt so konfiguriert war, dass Eclipse beim Build nur *.java-Files aus dem Source-Folder berücksichtigte und die *.html-Files eben nicht.

Um das Problem zu beheben hangelt man sich zu „Project Properties > Java Build Path > Source“. Dort entfernt man beim Source-Folder src/main/java das Include-Pattern **/*.java. Alternativ kann man dort natürlich auch **/*.html als Include-Pattern hinzufügen. Der Effekt ist der gleiche. Nach einem Redeploy und Neustart des Servers sollte die Anwendung wie gewünscht starten.

Wie kam das Include-Pattern dorthin?
Das Projekt hatte ich ursprünglich mit Eclipse erstellt und erst danach für Maven aufbereitet. In dieser Zeit war das Include-Pattern des Source-Folders leer. Später habe ich im POM des Projekts einige Libraries hinzugefügt. Um die Libraries im Projekt verwenden zu können, habe ich mit mvn eclipse:eclipse das Eclipse-Projekt neu generiert. Unter Verwendung der Standardkonfiguration generiert Maven die Classpath-Entries für den Source- und Resource-Folder allerdings mit den oben genannten Include- und Exclude-Patterns.

Um html-Files zu inkludieren, muss das Maven-Eclipse-Plugin entsprechend konfiguriert werden. Wie das geht ist in der Dokumentation des Maven-Eclipse-Plugin zu finden.

Mixed Maven-Multi-Module-Projects mit OSGi: Tycho vs. Apache-Felix-Bundle-Plugin

Multi-Module-Projekte vereinen immer unterschiedliche Softwaretechniken, die durch die wohl bekannte Layer-Architektur heutiger Anwendungen bedingt sind. Dabei wird die Anwendung in der Regel in die Module Core, Services, Persistence und UI aufgeteilt. Sehr häufig wird dabei ein und dieselbe Anwendung für mehrere Clients (z. B. GUI, Commandline und Web-Client) entwickelt. Das Projekt wird dann zusätzlich in die einzelnen GUI-Projekte web, cli und rcp aufgeteilt. Für Client-GUIs hat sich dabei Eclipse-RCP bewährt.

RCP-Client-Anwendungen werden im Gegensatz zu Webanwendungen häufig mit dem Ziel entwickelt, Fachlogik vom Server auf die Seite des Clients zu verlagern, insbesondere dann, wenn die Kommunikation mit dem Server aus Performancegründen unterbunden werden soll oder wenn Fachlogik offline bereitgestellt werden soll. Dann steht man spätestens beim Build und Deploy der Module jedoch vor dem Problem, die klassische Java-Welt mit der OSGi-Welt von Eclipse zu integrieren. Weil für die Entwicklung von RCP-Anwendungen alle Module als Plugin bzw. OSGi-Bundle bereitgestellt werden müssen, ist man mit der schwierigen Entscheidung konfrontiert, die einzelnen Module wie Core, Services und unter Umständen auch den Persistence-Layer in OSGi-Bundles zu gießen und diese letztendlich auch effizient zu bauen, zu paketieren und bereitzustellen.

Hier gibt es zwei Ansätze:
Werden die Anwendungen vollständig als OSGi-Anwendung entwickelt, sollte man auf jeden Fall einen Blick auf Maven Tycho werfen. Diese Variante kann ich für den obigen Fall zur Zeit allerdings noch nicht ganz uneingeschränkt empfehlen, weil Maven Tycho bei gemischten Multi-Module-Projekten noch einige Stolpersteine bereithält, die die Migration von bestehenden Java-Projekten nicht so einfach machen.

Werden allerdings lediglich Core und Service als OSGi-Bundle gewünscht, hat sich der folgende Ansatz bewährt.
Dabei werden Core- und Service-Layer weiterhin als klassische Maven-Java-Projekte entwickelt. Allerdings müssen nun die Manifest-Dateien der JARs um die fehlenden OSGi-Bundle-Einträge ergänzt werden. Diese Aufgabe überträgt man am besten an das Apache-Felix-Maven-Plugin. Es generiert die gewünschten OSGi-Bundle-Einträge für das endgültige JAR. Anschließend werden die so generierten Bundles in die Targetplatform des Zielprojekts deployt, sodass Core und Services zu guter Letzt als OSGi-Bundles in den RCP-Projekten verwendet werden können.

Auf diese Weise werden Risiken bei der Mavenisierung von gemischten RCP/OSGi-Builds erheblich reduziert.