Abbot Topics
Abbot framework for testing Java GUI components and programs
All materials Copyright © 2002-2011 All Rights Reserved Timothy Wall

This version 1.2.0-RC2

Tutorial 1: Unit Testing A Single GUI Component

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.

java -Dabbot.robot.auto_delay=200 -cp classes example.ArrowButtonTest
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.

See the full source code for the component example.ArrowButton, and its TestCase, example.ArrowButtonTest.

This project helped in a big way by
SourceForge Logo