So you're a fan of test-first development. Let's write a test for a component
that hasn't been written yet (well, it has, but let's pretend). The component
we want to write is a button with the following behavior:
Points in one of four directions
Fires an event when pressed once
Fires continuous events when held down
With this simple spec, we can outline a unit test for it:
package example;
import junit.extensions.abbot.*;
import abbot.tester.*;
// ComponentTestFixture automatically handles disposal of GUI components,
// among (many) other things. Always use it when testing GUI components.
public class ArrowButtonTest extends ComponentTestFixture {
public ArrowButtonTest(String name) { super(name); }
public static void main(String[] args) {
TestHelper.runTests(args, ArrowButtonTest.class);
}
}
We'll need to put the component into a frame for most tests, but there's
already a "showFrame" method in the ComponentTestFixture that we inherit.
That method puts a nice fat border around our component to make it visible,
although that's not strictly necessary. We'll need a ComponentTester to play
actions on our component, so add that to the setup:
private ComponentTester tester;
protected void setUp() {
tester = new ComponentTester();
}
protected void tearDown() {
// Default JUnit test runner keeps references to Tests for its
// lifetime, so TestCase fields won't be GC'd for the lifetime of the
// runner.
tester = null;
}
Now write the first test, which ensures that an action is fired when the
button is clicked.
import java.awt.event.*;
import javax.swing.*;
...
private String clickType;
public void testClick() {
ActionListener al = new ActionListener() {
public void actionPerformed(ActionEvent ev) {
clickType = ev.getActionCommand();
}
};
ArrowButton left = new ArrowButton(ArrowButton.LEFT);
ArrowButton right = new ArrowButton(ArrowButton.RIGHT);
ArrowButton up = new ArrowButton(ArrowButton.UP);
ArrowButton down = new ArrowButton(ArrowButton.DOWN);
left.addActionListener(al);
right.addActionListener(al);
up.addActionListener(al);
down.addActionListener(al);
JPanel pane = new JPanel();
pane.add(left);
pane.add(right);
pane.add(up);
pane.add(down);
// ComponentTestFixture provides the frame
showFrame(pane);
clickType = null;
tester.actionClick(left);
assertEquals("Action failed (left)", ArrowButton.LEFT, clickType);
clickType = null;
tester.actionClick(right);
assertEquals("Action failed (right)", ArrowButton.RIGHT, clickType);
clickType = null;
tester.actionClick(up);
assertEquals("Action failed (up)", ArrowButton.UP, clickType);
clickType = null;
tester.actionClick(down);
assertEquals("Action failed (down)", ArrowButton.DOWN, clickType);
}
If we were writing the ArrowButton from scratch, we'd have identified by this
test a few things that we need:
A set of constants for the arrow direction.
An addActionListener method (and probably a remove as well).
A constructor that takes the direction as its only argument.
Well, let's assume we went off and implemented those functions (this tuturial
is one about testing GUI components, not unit testing or XP in general).
After compiling, run the example.ArrowButtonTest class, and repeat the
code/test/debug cycle until the test passes.
Note that the auto-delay setting is only there to prevent the test from
running so fast you don't see it; normally you can omit it.
That leaves only the last bullet to test, repeated events when held down.
private int count = 0;
public void testRepeatedFire() {
ArrowButton arrow = new ArrowButton(ArrowButton.LEFT);
ActionListener al = new ActionListener() {
public void actionPerformed(ActionEvent ev) {
++count;
}
};
arrow.addActionListener(al);
showFrame(arrow);
Dimension size = arrow.getSize();
// Hold the button down for 5 seconds
tester.mousePress(arrow);
tester.actionDelay(5000);
tester.mouseRelease();
assertTrue("Didn't get any repeated events", count > 1);
}
Run the test case again, and (if you were writing ArrowButton from scratch)
the second test would fail. Go back and implement the repeated fire function
in example.ArrowButton, and debug and test until testRepeatedFire passes.
Now you've increased your confidence that subsequent changes to ArrowButton
which break the desired behavior will break the tests and you'll know about
it. You've also just documented the expected behavior, which can mean the
difference between someone understanding your code or someone using it
incorrectly based on out-of-date or missing documentation.