Wie man eine JavaFX Anwendung testet habe ich bereits im Tutorial JavaFX: Automatisierte GUI Test mit JUnit und TestFX erläutert. In diesem Tutorial möchte ich nun auf das Testen einer Oracle Java Swing Anwendung eingehen.
Testprojekt anlegen
Als erstes richte ich ein kleines Oracle Java 8 Projekt mit Apache Maven in der Entwicklungsumgebung Eclipse ein.
Blankes Maven Projekt in Eclipse erzeugen
Einfaches Swing Projekt erzeugen
Nach dem Eclipse das Maven Projekt mit der typischen Struktur angelegt hat erzeugen wir nun eine kleine GUI, welche wir mit AssertJ testen wollen.
Die “Anwendung” enthält zwei Eingabefelder für den Vornamen & den Nachnamen sowie eine Schaltfläche mit der Bezeichnung “say Hello”.
Dieses ist wirklich minimalistisch gehalten soll aber für dieses Tutorial ausreichen.
package de.swingtest; import java.awt.GridLayout; import java.awt.event.ActionListener; import java.io.Serializable; import javax.swing.*; /** * Einfache Klasse zum erzeugen eines {@link JFrame} zum eingeben des Vornamens * sowie des Nachnamens. Beim klicken auf den {@link JButton} wird ein * {@link JOptionPane} Dialog angezeigt. * * @author Stefan Draeger * */ public class SimpleSwingGui extends JFrame { /** * Da die Klasse JFrame das Interface {@link HasGetTransferHandler} * implementiert und dieses Interface wiederum das Interface * {@link Serializable} implementiert sollte man eine eindeutige ID für den * Prozess der Serialisierung hinzufügen. */ private static final long serialVersionUID = 3153235363948579984L; /** {@link JTextField} für die Eingabe des Vornamens **/ private JTextField vornameTextField; /** {@link JTextField} für die Eingabe des Nachnamens **/ private JTextField nameTextField; /** * Konstruktor */ public SimpleSwingGui() { init(); } /** * Das {@link JFrame} einrichten. */ private void init() { this.setTitle("SimpleSwingGui"); this.setBounds(0, 0, 350, 100); this.setLayout(new GridLayout(0, 2)); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); initComponents(); this.setVisible(true); } /** * Erzeugen der Eingabefelder und des Buttons sowie der {@link ActionListener} */ private void initComponents() { this.add(new JLabel(" Vorname")); vornameTextField = new JTextField(); vornameTextField.setName("vornameTextField"); this.add(vornameTextField); this.add(new JLabel(" Name")); nameTextField = new JTextField(); nameTextField.setName("nameTextField"); this.add(nameTextField); JButton sayHelloBtn = new JButton("say Hello"); sayHelloBtn.setName("sayHelloBtn"); sayHelloBtn.addActionListener(event -> { String message = String.format("Hallo %s %s", vornameTextField.getText(), nameTextField.getText()); int result = JOptionPane.showConfirmDialog(SimpleSwingGui.this, message, "Hinweis...", JOptionPane.OK_CANCEL_OPTION); String resultMsg = String.format("Button %s wurde geklickt", result == JOptionPane.OK_OPTION ? "OK" : result == JOptionPane.CANCEL_OPTION ? "ABBRECHEN" : "-undefined-"); System.out.println(resultMsg); }); this.add(sayHelloBtn); } }
AssertJ im Projekt einrichten
Da ich das Projekt mit Apache Maven erzeugt habe kann man hier bequem die Abhängigkeiten (Dependencies) des Projektes in der pom.xml hinzufügen.
<dependency> <groupId>org.assertj</groupId> <artifactId>assertj-swing-jide</artifactId> <version>3.8.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-swing-junit</artifactId> <version>3.8.0</version> <scope>test</scope> </dependency>
Es werden zusätzlich die Abhängigkeiten für JUnit & Hamcrest benötigt.
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.hamcrest</groupId> <artifactId>hamcrest-core</artifactId> <version>1.3</version> <scope>test</scope> </dependency>
Nachdem die Datei pom.xml um die oben genannten Abhängigkeiten erweitert wurde muss das Projekt neu gebaut werden, dazu wird der Befehl
mvn install
auf dem Projekt ausgeführt.
Ersten JUnit Test anlegen
Wollen wir also als Erstes die GUI mit den folgenden Funktionen testen:
- Eingabe von Vorname & Nachname,
- Betätigen der Schaltfläche,
- Öffnen des Hinweisdialoges,
- Betätigen der Schaltflächen “OK” & “Abbrechen”
package de.swingtest; import static org.junit.Assert.assertNotNull; import java.awt.event.KeyEvent; import org.assertj.swing.edt.GuiActionRunner; import org.assertj.swing.finder.JOptionPaneFinder; import org.assertj.swing.fixture.FrameFixture; import org.assertj.swing.fixture.JButtonFixture; import org.assertj.swing.fixture.JOptionPaneFixture; import org.assertj.swing.fixture.JTextComponentFixture; import org.assertj.swing.junit.testcase.AssertJSwingJUnitTestCase; import org.junit.Test; /** * Eine einfache Klasse zum Testen der SwingAnwendung {@link SimpleSwingGui}. * Der Test erweitert die Abstrakte Klasse * {@link org.assertj.swing.junit.testcase.AssertJSwingJUnitTestCase}. Mit dem * erweitern der Abstrakten Klasse müssen wir die Methode * {@link org.assertj.swing.junit.testcase.AssertJSwingJUnitTestCase#onSetUp} * implementieren. * * Zusätzlich habe ich noch die Methode * {@link org.assertj.swing.junit.testcase.AssertJSwingJUnitTestCase#onTearDown} * implementiert damit ich in dieser dann den JUnit Test aufräumen kann. * * * @author Stefan Draeger * */ public class TestSimpleSwingGui extends AssertJSwingJUnitTestCase { // Eine "Referenz" auf die SwingAnwendung. private FrameFixture window; @Override protected void onSetUp() { // Starten der SwingAnwendung SimpleSwingGui frame = GuiActionRunner.execute(() -> new SimpleSwingGui()); window = new FrameFixture(robot(), frame); window.show(); } @Override protected void onTearDown() { // Nach dem Beenden des Testfalls, alles Aufräumen. // Das heißt, es werden alle Fenster geschlossen, // es werden die Maustasten freigegeben, // es wird der Bildschirm freigegeben. window.cleanUp(); } @Test public void shouldBeShowInfodialog() { // Das JTextField mit dem Namen "vornameTextField" suchen und merken. JTextComponentFixture vornameTextField = window.textBox("vornameTextField"); // Wenn das Objekt nicht NULL ist dann gehts weiter... assertNotNull(vornameTextField); //In das JTextField die Buchstaben m,a,x eingeben. vornameTextField.pressAndReleaseKeys(KeyEvent.VK_M, KeyEvent.VK_A, KeyEvent.VK_X); // Das JTextField mit dem Namen "nameTextField" suchen und merken. JTextComponentFixture nachnameTextField = window.textBox("nameTextField"); // Wenn das Objekt nicht NULL ist dann gehts weiter... assertNotNull(nachnameTextField); //In das JTextField die Buchstaben m,u,s,t,e,r,m,a,n,n eingeben. nachnameTextField.pressAndReleaseKeys(KeyEvent.VK_M, KeyEvent.VK_U, KeyEvent.VK_S, KeyEvent.VK_T, KeyEvent.VK_E, KeyEvent.VK_R, KeyEvent.VK_M, KeyEvent.VK_A, KeyEvent.VK_N, KeyEvent.VK_N); // Die Schaltfläche mit dem Namen "sayHelloBtn" suchen und merken. JButtonFixture sayHelloBtn = window.button("sayHelloBtn"); // Wenn das Objekt nicht NULL ist dann gehts weiter... assertNotNull(sayHelloBtn); //Eine Klickaktion auf die Schaltfläche ausführen. sayHelloBtn.click(); JOptionPaneFixture optionPane = JOptionPaneFinder.findOptionPane().withTimeout(10).using(robot()); // Wenn das Objekt nicht NULL ist dann gehts weiter... assertNotNull(optionPane); // Wenn das Objekt "optionPane" nicht sichtbar ist dann wird eine Fehlermeldung "geworfen". optionPane.requireVisible(); // Die Schaltfläche mit dem Text "OK" suchen und merken. JButtonFixture okBtn = optionPane.buttonWithText("OK"); // Wenn das Objekt nicht NULL ist dann gehts weiter... assertNotNull(okBtn); //Eine Klickaktion auf die Schaltfläche ausführen. okBtn.click(); //Eine Klickaktion auf die Schaltfläche ausführen. sayHelloBtn.click(); optionPane = JOptionPaneFinder.findOptionPane().withTimeout(10).using(robot()); // Wenn das Objekt nicht NULL ist dann gehts weiter... assertNotNull(optionPane); optionPane.requireVisible(); // Die Schaltfläche mit dem Text "Abbrechen" suchen und merken. JButtonFixture abbrechenBtn = optionPane.buttonWithText("Abbrechen"); // Wenn das Objekt nicht NULL ist dann gehts weiter... assertNotNull(abbrechenBtn); //Eine Klickaktion auf die Schaltfläche ausführen. abbrechenBtn.click(); } }
Video
Während der Ausführung des Testes sollte die Tastatur und die Maus nicht betätigt werden, denn damit würde man den Testfall zu stoppen bringen.
Das Video habe ich in 1/4 der Geschwindigkeit abgespielt damit man etwas sieht. D.h. solltest du den Test nachbauen wirst du je nach Rechenleistung mehr oder weniger von der Ausführung sehen.
Ein komplexes Beispiel
Nachdem wir nun ein einfaches Beispiel erzeugt und erläutert haben wollen wir nun ein kleinen Businessprozess in einer Beispielanwendung “durchspielen”.
Die “Anwendung” hat folgende Funktion:
- aufnehmen einer Adresse,
- aufnehmen eines Alters (nur ganze Zahlen zwischen 1 und 100),
- Schaltfläche zum
- übernehmen,
- zurücksetzen der Daten
- Tabelle welche die Werte
- ID (Timestamp),
- Adresse,
- Alter,
- Zeitstempel (Formatiert) anzeigt
- Schaltflächen für
- exportieren (CSV Format), oder
- importieren (CSV Format) der Daten
- Schließen der Anwendung
Quellcode
Die nachfolgende Klasse befindet sich im Projekt “SwingTest” welches ich am Ende dieses Tutorials zum Download anbiete.
package de.swingtest.gui; import java.awt.Color; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.table.DefaultTableModel; import net.miginfocom.layout.AC; import net.miginfocom.layout.LC; import net.miginfocom.swing.MigLayout; public class ComplexSwingGui extends JFrame { private static final long serialVersionUID = -2973208254912255252L; private JTextArea adresseTextArea; private JTextField alterTextField; private JLabel formatErrorLbl; private JButton addBtn; private JButton exportBtn; private DefaultTableModel model; public ComplexSwingGui() { init(); } private void init() { this.setTitle("ComplexSwingGui"); this.setBounds(0, 0, 500, 680); MigLayout layout = new MigLayout(new LC().wrapAfter(1), new AC().align("center"), new AC()); this.setLayout(layout); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); initComponents(); this.setVisible(true); } private void initComponents() { adresseTextArea = new JTextArea(); adresseTextArea.setName("adresseTextArea"); adresseTextArea.setPreferredSize(new Dimension(250, 50)); this.add(getLblComponent("Adresse:", "adresseLbl", new JScrollPane(adresseTextArea))); alterTextField = new JTextField(); alterTextField.setName("alterTextField"); alterTextField.setPreferredSize(new Dimension(250, 25)); alterTextField.addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { char c = e.getKeyChar(); boolean isCharAllowed = ((c >= '0') && (c <= '9') || (c == KeyEvent.VK_BACK_SPACE) || (c == KeyEvent.VK_DELETE)); if (!isCharAllowed) { getToolkit().beep(); e.consume(); } }; @Override public void keyReleased(KeyEvent e) { boolean alterIsCorrect = false; try { String text = alterTextField.getText(); if (!text.isEmpty()) { Integer.parseInt(text); } alterIsCorrect = true; } catch (Exception ex) { // ex.printStackTrace(); } finally { addBtn.setEnabled(alterIsCorrect); formatErrorLbl.setVisible(!alterIsCorrect); } } }); this.add(getLblComponent("Alter:", "alterLbl", alterTextField)); formatErrorLbl = new JLabel("Es sind nur numerische Werte (0..9) von 1 bis 100 erlaubt."); formatErrorLbl.setName("formatErrorLbl"); formatErrorLbl.setForeground(Color.RED); formatErrorLbl.setVisible(false); this.add(formatErrorLbl); JPanel btnPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); btnPanel.setPreferredSize(new Dimension(450, 45)); addBtn = new JButton("Hinzufügen"); addBtn.setName("addBtn"); addBtn.addActionListener(event -> { long millis = System.currentTimeMillis(); model.addRow(new Object[] { String.valueOf(millis), adresseTextArea.getText(), alterTextField.getText(), getFormatedTimestamp(millis) }); }); btnPanel.add(addBtn); JButton resetBtn = new JButton("Zurücksetzen"); resetBtn.setName("resetBtn"); resetBtn.addActionListener(event -> { adresseTextArea.setText(""); alterTextField.setText(""); }); btnPanel.add(resetBtn); this.add(btnPanel); model = new DefaultTableModel(new Object[] { "ID", "Adresse", "Alter", "Zeitstempel" }, 0); JTable table = new JTable(model); table.setName("dataTable"); this.add(new JScrollPane(table)); JPanel btnPanel2 = new JPanel(new FlowLayout(FlowLayout.RIGHT)); btnPanel2.setPreferredSize(new Dimension(450, 45)); exportBtn = new JButton("Exportieren"); exportBtn.setName("exportBtn"); exportBtn.addActionListener(event -> { JFileChooser fileChooser = new JFileChooser(); int result = fileChooser.showSaveDialog(ComplexSwingGui.this); if (result == JFileChooser.APPROVE_OPTION) { File selectedFile = fileChooser.getSelectedFile(); try (BufferedWriter bw = new BufferedWriter(new FileWriter(selectedFile))) { int rowCount = model.getRowCount(); for (int i = 0; i < rowCount; i++) { long id = (long) Long.parseLong(model.getValueAt(i, 0).toString()); String adresse = (String) model.getValueAt(i, 1); adresse = adresse.replaceAll("\n", " "); int alter = Integer.parseInt(model.getValueAt(i, 2).toString()); String zeitstempel = (String) model.getValueAt(i, 3); String row = String.format("%d;%s;%d;%s\r\n", id, adresse, alter, zeitstempel); bw.write(row); bw.flush(); } } catch (Exception ex) { ex.printStackTrace(); } } }); btnPanel2.add(exportBtn); JButton importBtn = new JButton("Importieren"); importBtn.setName("importBtn"); importBtn.addActionListener(event -> { JFileChooser fileChooser = new JFileChooser(); int result = fileChooser.showOpenDialog(ComplexSwingGui.this); if (result == JFileChooser.APPROVE_OPTION) { File selectedFile = fileChooser.getSelectedFile(); try (BufferedReader br = new BufferedReader(new FileReader(selectedFile))) { String line = ""; while ((line = br.readLine()) != null) { System.out.println(line); String[] values = line.split(";"); model.addRow(new Object[] { values[0], values[1], values[2], values[3] }); } } catch (Exception ex) { ex.printStackTrace(); } } }); btnPanel2.add(importBtn); JButton clearBtn = new JButton("Tabelle leeren"); clearBtn.setName("clearBtn"); clearBtn.addActionListener(event -> { while (model.getRowCount() > 0) { model.removeRow(0); } }); btnPanel2.add(clearBtn); JButton closeBtn = new JButton("Schließen"); closeBtn.setName("closeBtn"); closeBtn.addActionListener(event -> { System.exit(0); }); btnPanel2.add(closeBtn); this.add(btnPanel2); } private JPanel getLblComponent(String lblBez, String lblName, JComponent comp) { JPanel pnl = new JPanel(new FlowLayout()); JLabel lbl = new JLabel(lblBez); lbl.setName(lblName); lbl.setPreferredSize(new Dimension(150, 25)); pnl.add(lbl); pnl.add(comp); return pnl; } private String getFormatedTimestamp(long millis) { DateFormat dateformat = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss"); return dateformat.format(new Date(millis)); } }
Erstellen der Testfälle
Es wird nun die Swing-Anwendung getestet. Dazu erzeuge ich ein neues Maven Projekt in Eclipse und werde dort die oben genannte Klasse hineinkopieren und verwenden.
Video
Im nachfolgenden YouTube Video möchte nun ausführlich erläutern wie man das Maven Projekt mit AssertJ, JUnit einrichtet.
Sollte eine Frage offen bleiben dann kannst du mir diese gerne als Kommentar oder per E-Mail hinterlassen.
package de.swingtest; import java.awt.event.KeyEvent; import java.io.File; import org.assertj.swing.core.matcher.JLabelMatcher; import org.assertj.swing.edt.GuiActionRunner; import org.assertj.swing.fixture.FrameFixture; import org.assertj.swing.fixture.JButtonFixture; import org.assertj.swing.fixture.JFileChooserFixture; import org.assertj.swing.fixture.JLabelFixture; import org.assertj.swing.fixture.JTableFixture; import org.assertj.swing.fixture.JTextComponentFixture; import org.assertj.swing.junit.testcase.AssertJSwingJUnitTestCase; import org.junit.Test; import de.swingtest.gui.ComplexSwingGui; public class TestComplexSwingGui extends AssertJSwingJUnitTestCase { private static final String EXPORT_DATEI = "diesIstEinDateiname.csv"; private FrameFixture window; int[] vorname = { KeyEvent.VK_A, KeyEvent.VK_X, KeyEvent.VK_SPACE }; int[] nachname = { KeyEvent.VK_U, KeyEvent.VK_S, KeyEvent.VK_T, KeyEvent.VK_E, KeyEvent.VK_R, KeyEvent.VK_M, KeyEvent.VK_A, KeyEvent.VK_N, KeyEvent.VK_N, KeyEvent.VK_ENTER }; int[] strasse = { KeyEvent.VK_U, KeyEvent.VK_S, KeyEvent.VK_T, KeyEvent.VK_E, KeyEvent.VK_R, KeyEvent.VK_S, KeyEvent.VK_T, KeyEvent.VK_R, KeyEvent.VK_A, KeyEvent.VK_S, KeyEvent.VK_S, KeyEvent.VK_E, KeyEvent.VK_SPACE, KeyEvent.VK_1, KeyEvent.VK_ENTER }; int[] ortTeil1 = { KeyEvent.VK_1, KeyEvent.VK_2, KeyEvent.VK_3, KeyEvent.VK_4, KeyEvent.VK_5, KeyEvent.VK_SPACE }; int[] ortTeil2 = { KeyEvent.VK_U, KeyEvent.VK_S, KeyEvent.VK_T, KeyEvent.VK_E, KeyEvent.VK_R, KeyEvent.VK_S, KeyEvent.VK_T, KeyEvent.VK_A, KeyEvent.VK_D, KeyEvent.VK_T }; boolean firstRun = true; long timestamp = 0L; @Override protected void onSetUp() { ComplexSwingGui frame = GuiActionRunner.execute(() -> new ComplexSwingGui()); window = new FrameFixture(robot(), frame); window.show(); if (firstRun) { timestamp = System.currentTimeMillis(); firstRun = false; } } @Override protected void onTearDown() { window.cleanUp(); // Export/Impot Datei löschen File fileToDetele = new File(EXPORT_DATEI); fileToDetele.delete(); } // @Test public void componentsShouldBeVisible() { JLabelFixture adresseLbl = window.label("adresseLbl"); adresseLbl.requireVisible(); adresseLbl.requireText("Adresse:"); JTextComponentFixture adresseTextBox = window.textBox("adresseTextArea"); adresseTextBox.requireVisible(); JLabelFixture alterLbl = window.label("alterLbl"); alterLbl.requireVisible(); alterLbl.requireText("Alter:"); JTextComponentFixture alterTextBox = window.textBox("alterTextField"); alterTextBox.requireVisible(); JLabelFixture formatErrorLbl = window.label(JLabelMatcher.withName("formatErrorLbl")); formatErrorLbl.requireNotVisible(); formatErrorLbl.requireText("Es sind nur numerische Werte (0..9) von 1 bis 100 erlaubt."); JButtonFixture addBtn = window.button("addBtn"); addBtn.requireVisible(); addBtn.requireEnabled(); addBtn.requireText("Hinzufügen"); JButtonFixture resetBtn = window.button("resetBtn"); resetBtn.requireVisible(); resetBtn.requireEnabled(); resetBtn.requireText("Zurücksetzen"); JTableFixture table = window.table("dataTable"); table.requireVisible(); JButtonFixture exportBtn = window.button("exportBtn"); exportBtn.requireVisible(); exportBtn.requireEnabled(); exportBtn.requireText("Exportieren"); JButtonFixture importBtn = window.button("importBtn"); importBtn.requireVisible(); importBtn.requireEnabled(); importBtn.requireText("Importieren"); JButtonFixture clearBtn = window.button("clearBtn"); clearBtn.requireVisible(); clearBtn.requireEnabled(); clearBtn.requireText("Tabelle leeren"); JButtonFixture closeBtn = window.button("closeBtn"); closeBtn.requireVisible(); closeBtn.requireEnabled(); closeBtn.requireText("Schließen"); } @Test public void tableHasCorrectColumns() { JTableFixture table = window.table("dataTable"); table.requireVisible(); table.requireColumnCount(4); JTableFixture columnID = table.requireColumnNamed("ID"); columnID.requireVisible(); JTableFixture columnAdresse = table.requireColumnNamed("Adresse"); columnAdresse.requireVisible(); JTableFixture columnAlter = table.requireColumnNamed("Alter"); columnAlter.requireVisible(); JTableFixture columnZeitstempel = table.requireColumnNamed("Zeitstempel"); columnZeitstempel.requireVisible(); } @Test public void shouldBeAddData() { addData(); } @Test public void shouldBeExportData() { addData(); exportData(); } @Test public void shouldBeRemoveData() { addData(); removeData(); } @Test public void shouldBeSImportData() { addData(); exportData(); removeData(); importData(); } @Test public void shouldBeSImportDataClearForm() { removeData(); importData(); } private void addData() { JTextComponentFixture adresseTextBox = window.textBox("adresseTextArea"); typeUpperCaseLetter(KeyEvent.VK_M, adresseTextBox); adresseTextBox.pressAndReleaseKeys(vorname); typeUpperCaseLetter(KeyEvent.VK_M, adresseTextBox); adresseTextBox.pressAndReleaseKeys(nachname); typeUpperCaseLetter(KeyEvent.VK_M, adresseTextBox); adresseTextBox.pressAndReleaseKeys(strasse); adresseTextBox.pressAndReleaseKeys(ortTeil1); typeUpperCaseLetter(KeyEvent.VK_M, adresseTextBox); adresseTextBox.pressAndReleaseKeys(ortTeil2); adresseTextBox.requireText("Max Mustermann\nMusterstrasse 1\n12345 Musterstadt"); JTextComponentFixture alterTextBox = window.textBox("alterTextField"); alterTextBox.pressAndReleaseKeys(KeyEvent.VK_3, KeyEvent.VK_1); alterTextBox.requireText("31"); JLabelFixture label = window.label(JLabelMatcher.withName("formatErrorLbl")); label.requireNotVisible(); JButtonFixture addBtn = window.button("addBtn"); addBtn.requireEnabled(); addBtn.click(); JTableFixture table = window.table("dataTable"); table.requireRowCount(1); } private void exportData() { JButtonFixture exportBtn = window.button("exportBtn"); exportBtn.requireEnabled(); exportBtn.click(); JFileChooserFixture fileChooserFixture = window.fileChooser(); fileChooserFixture.requireVisible(); fileChooserFixture.fileNameTextBox().setText(EXPORT_DATEI); fileChooserFixture.approveButton().click(); } private void removeData() { JButtonFixture clearBtn = window.button("clearBtn"); clearBtn.click(); JTableFixture table = window.table("dataTable"); table.requireRowCount(0); } private void importData() { JButtonFixture importBtn = window.button("importBtn"); importBtn.requireEnabled(); importBtn.click(); JFileChooserFixture fileChooserFixture = window.fileChooser(); fileChooserFixture.requireVisible(); fileChooserFixture.fileNameTextBox().setText(EXPORT_DATEI); fileChooserFixture.approveButton().click(); JTableFixture table = window.table("dataTable"); table.requireRowCount(1); } private void typeUpperCaseLetter(int letter, JTextComponentFixture textBox) { textBox.pressKey(KeyEvent.VK_SHIFT); textBox.pressAndReleaseKeys(letter); textBox.releaseKey(KeyEvent.VK_SHIFT); } }
Troubleshooting
Fehlerhafte Darstellung des Fensters
Beim Erzeugen der TestCases ist mir zuerst aufgefallen, dass das Fenster nicht korrekt dargestellt wurde. Ich habe zuerst das java.awt.FlowLayout verwendet, wenn die Anwendung “normal” gestartet wurde sah alles gut aus (siehe Bild oben) jedoch beim Ausführen des Testfalls wurde das komplette Layout verändert.
Nach einer kurzes suche, habe ich zu diesem Fehler nichts gefunden aber viele Beispiele zu diesem Testframework und dort habe ich gesehen das nirgends das FlowLayout verwendet wird. Daher gehe ich mal davon aus das der Fehler bekannt ist.
Ein Wechsel vom FlowLayout zum MigLayout brachte den erhofften Erfolg.
Diese Lösung ist für das erzeugen von Testfällen in bereits bestehende Projekte eigentlich ein K.O. Kriterium.
ComponentLookupException: Unable to find component using matcher
Wenn man einen Test auf einer java.awt.Component ausführen möchte, so “holt” man sich das Objekt vom aktuellen Fenster mit:
JLabelFixture label = window.label("testLbl"); label.requireNotVisible();
Wenn dieses Objekt aber nicht sichtbar ist dann erhält man folgende Exception
org.assertj.swing.exception.ComponentLookupException: Unable to find component using matcher org.assertj.swing.core.NameMatcher[name='testLbl', type=javax.swing.JLabel, requireShowing=true]. Component hierarchy: de.swingtest.gui.ComplexSwingGui[name='frame0', title='ComplexSwingGui', enabled=true, visible=true, showing=true] javax.swing.JRootPane[]
Der wichtige Teil dieser Exception ist dass, das Framework erwartet das die Komponente sichtbar ist (“requireShowing=true”).
Wie kann man also eine Komponente suchen (und im Idealfall finden) welche nicht sichtbar ist?
Dafür gibt es die Matcher für so ziemlich jede JComponent gibt es einen entsprechenden Matcher, diese beginnen mit <JComponentName>Matcher.
In diesem Fall musste ich den JLabelMatcher verwenden.
JLabelFixture label = window.label(JLabelMatcher.withName("testLbl")); label.requireNotVisible();
Nun wurde die Komponente gefunden under Test war “grün”.
Download
Damit man bequem das gesamte Tutorial durchspielen kann, möchte ich hier nun das Eclipse Projekt zum Download anbieten.
Quellen und Referenzen
Auf der Seite der Entwickler findet man einen Link zu den Java Doc’s sowie zu den Seiten wie man das Testframework einrichtet.
Fazit
Mit dem Testframework AssertJ und JUnit kann man relativ einfach und vor allem schnell einen Testfall erzeugen. Da ich in meinen Projekten immer (bzw. wenn möglich) Apache Maven verwenden kann ich diese Testfälle einfach beim Build automatisch starten.