|
Einleitung
In diesem Artikel beschreibe ich, wie
FitNesse als Werkzeug für automatisierte
Kunden-Tests in eine (Java-) Entwicklungs-Umgebung eingebettet wurde.
Vor etwa 6 Wochen hatte ich mich entschlossen,
FitNesse in einem von mir betreuten
Entwickler-Team von 4 Personen einzusetzen. "Endlich" muß ich sagen, denn seit 1 Jahr
suchte ich bereits nach geeigneten Anforderungen: der stark geschlossene Framework
(=Eigenentwicklung) hatte uns schon bei JUnit das
Leben schwer gemacht und er wehrte sich lange Zeit erfolgreich gegen
FitNesse (ich wollte technisch gesehen einen
einfachen Einstieg in die Thematik und nicht ein Refactoring des Frameworks
auslösen - noch nicht).
Die neuen Anforderungen führten zu komplexen
numerischen Berechnungen - ideal, um Test-Fälle vom Kunden beschreiben zu lassen
und diese mittels FitNesse zu automatisieren.
Rein "handwerklich" war mir FitNesse bzw.
Fit aus eigenen Versuchen und Übungen
gut vertraut: die Test-Seiten im FitNesse-Wiki,
die verschiedenen Fixtures
(inklusive FitLibrary) waren keine Hürde -
auch Dank der empfehlenswerten
Literatur dazu:
(daneben findet man z.B. mit Google
viele Links auf entsprechende Tutorials)
Eigentlich keine Tipps konnte ich allerdings dazu finden, wie man
FitNesse (bzw. eigentlich
die Idee, die damit verwirklicht wird) in eine stark automatisierte
Entwicklungs- und Build-Umgebung eines Teams einbettet.
Und genau das möchte hier beschreiben: wir gehen gemeinsam die
aneinandergereihten Bausteine entlang; angefangen beim Kunden zeige ich Schritt
für Schritt, wie man FitNesse in einen (vorhandenen!) Entwicklungs-Prozeß
integrieren kann. Viel Spaß auf dem Weg!
(für Ihre Rückmeldung zu diesem Artikel erreichen Sie mich immer per Email an
h.franzke@t-online.de)
Seitenanfang
Abgrenzung
Das folgende werden Sie in diesem Artikel nicht finden:
(bzw. setze ich entsprechende Kenntnisse und Erfahrungen voraus)
Wie gesagt: ich konzentriere mich auf die Integration und Einbettung
der Automatisierten Kunden-Tests mittels FitNesse in eine
Entwicklungs-Umgebung.
Ziel der Integration ist ein hohes Maß an Automatisierung,
die vor allem den Entwicklern ein schnelles Feedback bietet bei minimalem
Zeiteinsatz ihrerseits.
Seitenanfang
Übersicht
Um die Sache zu vereinfachen, sollte der FitNesse-Server auf dem
Build-Rechner laufen (in meinem Fall erreichbar über Port 4000):
Abb. 01: FitNesse auf dem Build-Rechner;
(Anmerkung: der Begriff "Projekt" steht im folgenden für ein abgeschlossenes
Software-Paket, das von einem Entwickler-Team erstellt wird;
ein Team kann in diesem Sinne für mehrere "Projekte" verantwortlich sein;
manche würden in diesem Sinne vielleicht auch "Produkt" sagen)
Abb. 01 zeigt:
- FitNesse-Server: hier verwaltet der Kunde (mit Unterstützung
aus dem Projekt-Team, durch den Coach oder spezielle Tester) seine Test-Fälle
in den Wiki-Seiten;
- Ant: für den automatisierten Build eines Projekts, d.h.
Erzeugung eines lieferbaren Pakets gemäß Kundenwunsch;
(CruiseControl oder ähnliches wird der Einfachheit halber nicht gezeigt)
- CVS: als Source-Code-Repository;
- Eclipse: als Entwicklungs-Umgebung, die auf den Rechnern der
Entwickler läuft;
Für ein einfaches Zusammenspiel der gezeigten Teile eignen sich ein paar
einfache Konventionen. Beginnen wir bei der Struktur der Wiki-Seiten im
zentralen FitNesse-Server.
Seitenanfang
FitNesse zentral
Der FitNesse-Server soll alle Projekte unterstützen, die diesen Build-Rechner
benutzen.
Für die Strukturierung der (Test-) Seiten im FitNesse-Wiki lassen wir uns von
folgenden Überlegungen leiten:
- kann man Projekte nach den Entwickler-Teams gruppieren,
die dafür zuständig sind;
- jede Projekt-Gruppe enthält wohl mindestens ein Projekt,
in der Regel wohl mehrere;
Anmerkung: statt "Projekt-Gruppe" könnte man auch
"Produkt-Familie" sagen;
- innerhalb eines Projekts sind aus Test-Sicht verschiedene Themenbereiche
interessant;
- in jedem Themenbereich gibt es dann die entsprechenden Test-Seiten
(oder auch noch weitere Unter-Gruppierungen davon);
Mit diesen Überlegungen kommen wir zu dieser Struktur:
Abb. 02: Struktur im Zentralen FitNesse-Server;
HINWEISE zur Struktur (mit Ausschnitten aus den Wiki-Seiten):
|!c '''List of all Projects using this FitNesse-Instance'''|
|!c '''Project Name''' | '''Brief Description''' |
|!c ProjectGroupHorst | Horst's Demo-Projects; |
Beispiel: so sieht es aus ...
!3 List of available Tutorial Projects:
| '''Tutorial-Name''' |!c '''Description''' |
| ^ProjectUserStoryList | example to test the TDD tutorial "User Story List";|
----
!****> '''Test-Utilities for whole Project-Group''':
'''Variables'''
!define projectGroupName {Horst's}
!define projectGroupHOME {/home/Horst/06-01-FitNesse-Examples}
!define COLLAPSE_SETUP {true}
!define COLLAPSE_TEARDOWN {true}
Beispiel: so sieht es aus ...
!3 List of Test-Suites:
| '''Test-Suite''' |!c '''Focus of Tests within Suite''' |
| '''^SuiteForTestsOnLists'''| check requirements on story lists; |
!****> '''Test-Utilities for this Project''':
'''Classpaths'''
!path ${projectGroupHOME}/userStoryList/fitnesse/*
'''Variables'''
!define projectName {User Story List}
'''Fixtures'''
!fixture de.franzke.userStoryList.AboutSourceCodeUnderTest
!fixture de.franzke.userStoryList.StoryListAdministration
!fixture de.franzke.userStoryList.UserStoryListing
'''^SetUp'''
'''^TearDown'''
****!
Beispiel: so sieht es aus ...
(Die Test-Seiten selbst enthalten dann die spezifischen Test-Fälle
oder -Szenarien.)
FitNesse muß "nur" den aktuellen
Domain- und Fixture-Code zum Projekt
finden: dazu stellen wir einen einfachen Brückenkopf zur
Verfügung.
Seitenanfang
Brückenkopf zum Build
Einen "Brückenkopf" zu FitNesse gibt es
in jedem einzelnen Projekt (in meinen Beispielen hier das
Tutorial-Projekt 'userStoryList') in Form eines speziellen Verzeichnisses.
|-/home/Horst/06-01-FitNesse-Examples/userStoryList
| =================================================
| |
...
| |
| |-fitnesse
| | ========
| | |-fitlibrary.jar
| | |-fitnesse.jar
| | |-userStoryList-fitnesse-v-1-4-1.jar // (1)
| | |-userStoryList-tests-v-1-4-1.jar // (2)
| | |-userStoryList-v-1-4-1.jar // (3)
| |
...
FitNesse erwartet hier den kompletten Code,
der für die Tests notwendig ist:
Das war's schon zum Thema "Brückenkopf", d.h. wir nähern uns langsam
dem Ende unserer Wanderung von der Kunden-Seite zur Entwickler-Seite.
Etwas interessanter wird das nächste Stück Weg, wenn wir uns
ansehen, wie mit Automatisierung durch Ant
dieses Verzeichnis mit dem aktuellen Code gefüllt wird.
Seitenanfang
Ant-Build-Target
Da ich nicht ein komplettes Build-Skript (mit allen möglichen Targets)
vor Ihnen ausbreiten möchte, werde ich nur das Target zeigen,
das für den aktuellen Code das Brückenkopf-Verzeichnis korrekt füllt.
(ich gehe davon aus, daß das Einbauen dieses Targets in ein bestehendes
Build-Skript keine Probleme bereitet; siehe auch
Pragmatic Project Automation)
<!-- //////////////////////////////////////////////////////////////////// -->
<!-- Publish Code to FitNesse; -->
<!-- //////////////////////////////////////////////////////////////////// -->
<target name="publish.to.fitnesse"
description="after a clean test-run publish all jar's to fitnesse;"
depends="_set.standard.properties, // (1)
_set.project.properties, // (2)
_get.sources, // (3)
_compile.and.jar.domain, // (4)
_compile.and.jar.junit, // (5)
_run.junit, // (6)
_compile.and.jar.fitnesse"> // (7)
<delete dir="${fitnesse.dir}" /> // (8)
<mkdir dir="${fitnesse.dir}" />
<copy todir="${fitnesse.dir}">
<fileset dir="${build.jar.dir}"> // (9)
<include name="**/*.jar"/>
<exclude name="**/CVS/**/*"/>
</fileset>
</copy>
<echo message="Target 'publish.to.fitnesse' finished OK." />
</target>
<!-- //////////////////////////////////////////////////////////////////// -->
<!-- Compile FitNesse Code and create the jar-file; -->
<!-- //////////////////////////////////////////////////////////////////// -->
<target name="_compile.and.jar.fitnesse"
depends="_set.standard.properties,
_set.project.properties">
<javac destdir="${build.fitnesse.dir}"> // (10)
<classpath refid="vendor.lib.path"/> // (11)
<classpath refid="domain.class.path"/>
<classpath refid="test.class.path"/>
<src path="${source.dir}/${project.module}"/>
<patternset refid="sources.fitnesse"/>
</javac>
<jar jarfile="${jar.fitnesse}"> // (12)
<fileset dir="${build.fitnesse.dir}">
<include name="**/*.class" />
</fileset>
</jar>
<echo message="Target '_compile.and.jar.fitnesse' finished OK." />
</target>
So ... wir haben die Test-Seiten im FitNesse,
die Verbindung zum zu testenden Code (inkl. der notwendigen Fixtures)
und wir haben den aktuellen Code automatisch an der richtigen Stelle
für FitNesse bereitgestellt.
Fehlt noch der Quell-Code. Der kommt jetzt:
Seitenanfang
Fixture-Folder
Die Konventionen für den Quell-Code der Fixtures sind sehr einfach:
|-/home/Horst/06-01-FitNesse-Examples/userStoryList
| =================================================
| |
...
| |
| |-sources
| | =======
| | |
| | |-userStoryList
| | | =============
| | | |
| | | |-fitnesse
| | | | ========
| | | | |
| | | | |-de
| | | | | ==
...............
| | | |
| | | |-src
| | | | ===
| | | | |
| | | | |-de
| | | | | ==
...............
| | | |
| | | |-test
| | | | ====
| | | | |
| | | | |-de
| | | | | ==
...............
Wenn wir an dieser Stelle aufhören, dann muß jeder Entwickler erst
in das Repository einchecken und einen offiziellen Build laufen lassen,
bevor er das Feedback von den FitNesse-Tests sehen kann. Das dauert zu lange!
Deshalb wollen wir noch schnell die Möglichkeit beschreiben, die
zentralen FitNesse-Tests lokal auf dem Desktop des Entwicklers laufen zu
lassen.
Seitenanfang
FitNesse lokal
Es gibt ein Eclipse-Plugin für reine Fit-Tests
(fitrunner). Leider hilft uns das mit
FitNesse nicht.
Ich schlage deshalb vor, der Empfehlung bei FitNesse zu folgen:
- FitNesse auf dem Entwickler-Desktop lokal installieren und starten
(z.B. auf Port 4040);
- an geeigneter Stelle das entsprechende Projekt von Zentralen FitNesse-Server
importieren (nähere Beschreibung dazu siehe
FitNesse-Import);
Für mein Beispiel funktioniert das so:
(Zentraler FitNesse-Server auf Port 4000 und lokaler auf Port 4040)
!3 Settings to provide local support for remote import:
'''Variables'''
!define developmentWorkspace {/home/franzkeh/workspace.2-1-1}
----
!3 List of Imported Projects
* ^ImportProjectUserStoryList
Beispiel: so sieht es aus ...
!3 Settings to provide local support for remote import:
'''Variables'''
!define COLLAPSE_SETUP {true}
!define COLLAPSE_TEARDOWN {true}
!define projectName {userStoryList}
'''Classpaths'''
!path ${developmentWorkspace}/${projectName}/classes
!path ${developmentWorkspace}/${projectName}/vendor/lib/fitlibrary.jar
!path ${developmentWorkspace}/${projectName}/vendor/lib/fitnesse.jar
----
!3 Contents
!contents -R
Beispiel: so sieht es aus ...
Fertig! Jetzt kann mit enger zeitlicher Koppelung der wachsende Code
gegen die FitNesse-Tests geprüft werden - einfach auf Knopfdruck.
ACHTUNG: leider werden die lokalen Imports NICHT automatisch
aktualisiert! D.h. Änderungen im Zentralen FitNesse-Server könnten
übersehen werden.
Wer als Entwickler nicht immer wieder aktiv in der Zentrale nachsehen
möchte (nur um festzustellen, daß sich nichts geändert hat!),
dem kann mittels RSS-Feed einfach und schnell geholfen werden:
Details zum FitNesse-RSS-Feed im Original hier.
Bis jetzt haben wir viel erreicht: auf Knopfdruck werden die Kunden-Tests
zentral oder lokal alle ausgeführt.
Aber was ist mit dem Standard-Builds,
z.B. für automatische Versionierung, Paketierung bzw. den Versand?
Auch hier gibt es eine einfache Möglichkeit, FitNesse
zu integrieren:
Seitenanfang
Integration
Ziel ist: bei allen Lieferungen (egal ob Test, Demo, oder Produktion)
ist sichergestellt, daß alle Kunden-Tests erfüllt sind.
Dazu wird FitNesse an den passenden Stellen
im Ant-Build-Skript so eingebunden,
daß wie bei JUnit der Build-Lauf abbricht, falls
ein Kunden-Test fehlschlägt.
Die Einbindung in ein vorhandenes Build-Skript werde ich hier nicht besprechen
(dazu müßte ich ja Ihr Build-Skript kennen).
Aber das Prinzip kann ich Ihnen zeigen.
<?xml version="1.0" ?>
<!--
#######################################################################
Ant-Build-File: Fitnesse Test-Runner;
=================================================================
14.05.2006 Horst Franzke (www.horstfranzke.de)
#######################################################################
-->
<project name="Fitnesse-Test-Runner" default="run.fitnesse" basedir=".">
<property name="fitnesse.test.or.suite.to.start.at" // (1)
value="ProjectGroupHorst.ProjectUserStoryList" />
<property name="report.build" // (2)
value="." />
<property name="fitnesse.jar.dir" // (3)
value="/home/tools/fitnesse" />
<target name="run.fitnesse"
if="fitnesse.test.or.suite.to.start.at"> // (4)
<property name="fitnesse.report.dir" // (5)
value="${report.build}/fitnesse" />
<mkdir dir="${fitnesse.report.dir}" />
<property name="result.file.for.fitnesse.html" // (6)
value="${fitnesse.report.dir}/fitnesse-result.html" />
<property name="fitnesse.server" // (7)
value="localhost" />
<property name="port.of.fitnesse.server"
value="4000" />
<path id="fitnesse.classpath">
<fileset dir="${fitnesse.jar.dir}"> // (8)
<include name="fitnesse.jar" />
<include name="fitlibrary.jar" />
</fileset>
</path>
<echo message="Fitnesse running on ${test.or.suite.to.start} ..." />
<java classname="fitnesse.runner.TestRunner"
classpathref="fitnesse.classpath"
fork="true"
dir="."
failonerror="true" // (9)
>
<arg line="-v // (10)
-html ${result.file.for.fitnesse.html}
${fitnesse.server} ${port.of.fitnesse.server}
${fitnesse.test.or.suite.to.start.at}"/>
</java>
<echo message="Target 'run.fitnesse' finished OK." />
</target>
</project>
Geschafft! Rein technisch habe ich jetzt nichts mehr zu erzählen.
Bleiben nur noch ein paar kurze Gedanken als Nachbetrachtung ...
Seitenanfang
Zufrieden
Alles im allem sind die Beteiligten bei meinen Teams mit diesem Einsatz von
FitNesse sehr zufrieden.
Allerdings sehe ich für die Zufriedenheit durchaus unterschiedliche Gründe -
je nach Blickwinkel der Personen:
Trotz all dem Erreichten bleiben noch ein paar Offene Fragen ...
Seitenanfang
Offene Fragen
Im Moment sind mir noch zwei offene Fragen zu diesem Thema geblieben:
- ich würde gerne die FitNesse-Tests versionieren;
damit könnte ich dann eine bestimmte Version des Domain-Code
mit einem bestimmten Satz von Kunden-Tests in Beziehung
setzen; einen ersten Hinweis auf einen möglichen Lösungsansatz habe ich bei
www.fitnesse.testmanager.info/ gefunden;
- der zweite offene Punkt hat eigentlich mit dem ersten zu tun,
er wirkt sich nur sehr viel "praktischer" aus:
schreibt ein Kunde neue Tests zu noch nicht implementierten
Funktionalitäten, dann scheitern alle Build-Läufe mit FitNesse-Beteiligung
in diesem Projekt;
im Moment hilft nur: über das Property 'fitnesse.test.or.suite.to.start.at'
FitNesse abschalten;
- zum Thema "Security": es wird eine Möglichkeit der
Authentifizierung angeboten;
allerdings empfinde ich sie als etwas "unhandlich" aus der Sicht eines
Server-Administrators: sie ist getrennt von den Sicherheits-Mechanismen
(Paßwort-File) des Servers und die Möglichkeit von Kunden, sich ihr
eigenes Paßwort zu setzen ist standardmäßig nicht gegeben;
Das bedeutet aber nicht, daß wir den Einsatz von
FitNesse einschränken oder gar stoppen.
Im Gegenteil: mit all den gefundenen Vorteilen wir werden den Einsatz weiter ausbauen!
Seitenanfang
Zusammenfassung
In meinem Fall hat sich die Einführung von FitNesse
zur Automatisierten Einbettung in einen Build-Prozeß gelohnt:
- der Kunde hat noch mehr Vertrauen gewonnen;
- die Kommunikation zwischen Kunde und Entwickler ist noch besser
und konstruktiver geworden;
- wir sehen positive Auswirkungen in der Struktur unseres Systems;
- die Nacharbeiten in den FitNesse-getesteten Komponenten gehen gegen Null;
(im Moment sind sie Null!)
Damit sind wir unserem eigentlichen Ziel wieder einen Schritt näher gekommen:
zielstrebig und mit immer mehr Effizienz dem Kunden das geben, was er
wirklich von uns erwartet: "Gute Solide Anpassungsfähig Software"!
Seitenanfang
Verweise
Folgende Quellen wurden zum Teil zitiert bzw. werden empfohlen zur Vertiefung und
für weiterführende Informationen:
Seitenanfang
|