Automatisiertes Testen von Swing-Komponenten mit JFCUnit

Don't click to test!

Michael Jürgens

JFCUnit ist eine Bibliothek zum Durchführen JUnit-kompatibler Tests auf Swing basierender Komponenten. Obschon die Bibliothek erst in der Version 0.2.1pre-beta verfügbar ist, lassen sich bereits einige Testsituationen mit JFCUnit abdecken. Um diese Testsituationen einzugrenzen, werden die durch JFCUnit unterstützten Testprozesse und die Struktur von JFCUnit erläutert. Weiter wird die Benutzung der Bibliothek anhand zweier Beispiele demonstriert.

Unter dem Begriff Unit-Tests sind auf kleine Code-Komponenten lokalisierte Tests zum Verifizieren von Code-Funktionalität unter Annahme gewisser Rahmenbedingungen zu verstehen. Sie unterscheiden sich von funktionalen Tests, die die Korrektheit eines gesamten Softwaresystem oder einer funktionalen Teilkomponente prüfen. Das derzeit wohl bekannteste Framework zur Unterstützung von Unit-Tests heißt JUnit [1] und wurde von Kent Beck und Erich Gamma entwickelt.

Die JUnit-Erweiterung JFCUnit wurde initial von Matt Caswell und Greg Houston in Leben gerufen und zur Zeit wird sie maßgeblich von Vijay R. Aravamudhan weiter entwickelt. Sie ist unter der GNU Lesser General Public License (LGPL) über die Projekt-Homepage (siehe [2]) frei verfügbar. Sie bietet im wesentlichen Unterstützung beim automatisierten Testen auf Swing basierender Komponenten durch Methoden zum

  • Bestimmen von aktuell geöffneten Window- und Dialog-Objekten,
  • Finden von Button-, Label- oder benannten oder typisierten Component-Objekten,
  • Simulation von Eingabeereignissen wie das Klicken eines Buttons oder das Betätigen der Tastatur.

JFCUnit wird als ZIP Archiv mitsamt Sourcecode und einer kurzen, einführenden Dokumentation ausgeliefert. JFCUnit selbst besteht im wesentlichen aus der Testklasse JFCTestCase und einer unterstützenden Klasse JFCHelper. Die Klasse JFCTestCase bietet den Rahmen für Testfälle und enthält die interne Thread-Steuerung zu einem Testfall. Die Klasse JFCHelper bietet die oben beschriebene Funktionalität für den Zugriff auf Swing-Komponenten. Die Funktionalität von JFCUnit erschließt sich durch Implementieren eines TestCase der von der Klasse junit.extensions.jfcunit.JFCTestCase statt von der Klasse junit.framework.TestCase erbt.

Zur Durchführung des Testvorganges wird der AWT Event Handling Thread durch JFCUnit geblockt und kann bei Bedarf durch den Entwickler durch Aufruf der Methode awtSleep() für einen einstellbaren Zeitraum wieder freigeschaltet und danach wieder geblockt werden. Auf diese Weise ist es möglich, Veränderungen im GUI zu veranlassen, diese Veränderungen durch den AWT Event Handling Thread ausführen zu lassen und anschließend die Änderungen zu prüfen. Dieser Aufbau erlaubt es also zum Beispiel, konkret zuerst ein Fenster zu öffnen, dieses durch den AWT Event Handling Thread darstellen zu lassen, dann in dem Fenster den Klick auf einen Button zu simulieren und schließlich dem AWT Event Handling Thread Zeit zum Verarbeiten des Klickereignis zu geben.

Abb. 1: Der zu testende Dialog ButtonApp

Listing 2 zeigt einen Test für diesen Dialog. In der für jeden Testfall zur Vorbereitung aufgerufenen Methode setUp() wird zuerst der Dialog konstruiert und nach Aufruf von awtSleep(), wie oben beschrieben, auch dargestellt. Die eigentlichen Testmethode lokalisiert dann das Eingabefeld und das Ausgabefeld über die entsprechenden Namen. Anschließend wird über setText() die Eingabe simuliert, die dann mittels awtSleep() dargestellt wird. Die Simulation des Drückens des Schalters Process wird über die Methode enterClickAndLeave() der Klasse JFCTestHelper durchgeführt. Nachdem dann abermals über awtSleep() der Kontrollfluss an den AWT Event Handling Thread übergeben wurde, wird verifiziert, ob sich nun im Ausgabefeld der korrekte Wert befindet.

Listing 1: Test des ButtonApp-Dialoges

java junit.textui.TestRunner jm.jfcunit.ButtonAppTest

Auch wenn obiges Beispiel eher als funktionaler Test anzusehen ist, da in diesem Fall relativ große, aus vielen assoziierten Teilen zusammengesetzte Objekte getestet werden, die durchaus als funktionale Teilkomponenten eines Gesamtsystems zu betrachten sind, so ist der Einsatz von JFCUnit für Tests auf kleinere Komponenten durchaus möglich. Auf der beiliegenden CD ist als ein solches Beispiel ein Eingabefeld für ganzzahlige, positive Beträge aufgeführt, welches nur Ziffern als Zeichen zulässt und die eingegebene Zahl als eine durch Punkte getrennte endliche Ziffernfolge in der Form ###.###.###.### darstellt. Dieses Eingabefeld soll auf Ziffern-, Buchstaben-, Cursor- und Backspace-Tastendrücke korrekt reagieren. Die vielfältigen Testsituationen, die sich durch diese Tastenkombinationen ergeben, sind von Hand nur mühsam zu testen. Das automatisierte Testen mittels JFCUnit kann hier den Entwicklungsvorgang für solche Komponenten erheblich beschleunigen.

Um diesen Testvorgang durchführen zu können, ist die Simulation von Tastatureingaben notwendig, die zur Zeit nur teilweise in JFCUnit abgepictureet werden. So unterstützt JFCUnit bislang die Simulation von normalen Tasten. Unter einer normalen Taste ist hierbei eine Taste zu verstehen, die einen Unicode-Wert repäsentiert. Solche Tasten unterscheiden sich von den sogenannten Actionkeys, die keine Unicode-Zeichen-Repräsentation besitzen. So sind bespielsweise die Tasten Cursor-Links und Cursor-Rechts Actionkeys. Die Simulation dieser Tasten kann jedoch analog zu JFCUnit über durchgeführt werden (Listing 2). Bei dieser Simulation ist darauf zu achten, dass alle drei KeyEvents nacheinander zu erzeugen sind. Im Gegensatz zur JFCUnit Implementierung wird der in den systemnäheren KeyEvents vom Typ KeyEvent.KEY_PRESSED und KeyEvent.KEY_RELEASED der KeyCode anstelle der Konstante KeyEvent.VK_UNDEFINED verwendet. Auf diese Weise werden auch obige Actionkeys korrekt unterstützt. Mittels dieser obigen, ergänzenden Methode im TestCase ist es dann möglich, beispielsweise über die folgenden Methode aus Listing 3 einen umfangreicheren Test zur Funktionsweise des Eingabefeldes auszuführen.

Listing 2

public void testBackspace() throws Exception {
assertEquals(app.getInputField().getFormattedValue(), "");

JTextField money = app.getInputField();
JFCTestHelper.sendString(this, money, "1234", AWTSLEEPTIME);
assertEquals("1.234", app.getInputField().getFormattedValue());

typeKeyWithCode(money, KeyEvent.VK_BACK_SPACE);
assertEquals("123", app.getInputField().getFormattedValue());
}
Geschrieben von
Michael Jürgens
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.