Unit Testing: User Interfaces

One sticking point a lot of people seem to have is how to unit test a user interface.  I’ve read many articles by TDD advocates saying that you absolutely need to have unit tests for your GUI components, and then I’ve also read articles from the RAD tool crowd who say that because of the method of genesis the cost of testing out weighs the benefit.  While both groups are certain to remain dead-set against each other, I prefer to take a more pratical approach.  I do tend to disagree with the TDD folks that all tests must be written first, as long as there is sufficiant coverage I don’t really care when the test gets written.  I believe that it is one of the many jobs of a professional engineer to know when which approach is appropriate for the environment they are operating in.   However I strongly disagree with the RAD folks who say that because their UI code was computer generated, it doesn’t need testing.  Unit testing is about validating functional behavour, now RAD tools will layout widgets all day long, but they make no guarentee as to what happens when you click a button.

A hybrid approach

Now when looking a the visual appeal of a panel, there is no tool that is better than the human eye.  Granted it sometimes does depend on the human, but there are not going to be any tools out there that can ‘test’ for style, continuity etc..  What you can test is the outcome of user interaction. For example:


import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JList;
import javax.swing.JPanel;

public class ExamplePanel extends JPanel {

 private JButton clearButton = new JButton("Clear");
 private DefaultListModel listModel = new DefaultListModel();

 public ExamplePanel() {
 super(new BorderLayout());

 listModel.addElement("test");
 listModel.addElement("test2");
 listModel.addElement("test3");

 add(new JList(listModel), BorderLayout.CENTER);
 add(clearButton, BorderLayout.SOUTH);

 clearButton.addActionListener(new ActionListener() {

 public void actionPerformed(ActionEvent e) {
 listModel.clear();
 }

 });

 }

}

Now when you look at this class you will notice that the only testable function of the panel is the constructor.  So you will probably start off with a testcase that looks like:

public class ExamplePanelTest extends TestCase {

 public void testExamplePanel() {
    assertNotNull(new ExamplePanel());
 }

}

And you would see in your coverage report that you have covered 87% of the panel, that’s great right?  Well not really, all you’ve tested is that when you new this object an exception isn’t thrown and that really is insufficient for proving that your panel does what you’ve intended.  Ideally we would like to know what happens when somebody clicks the “Clear” button.

I’ve attached to the bottom of this post a file that contains some convenience methods for iterating through the java UI containers to locate widgets of interest. Using this class allows us to write more comprehensive test cases. For example, this is how I would test the above class:

public class ExamplePanelTest extends TestCase {

 public void testExamplePanel() {
 ExamplePanel panel = new ExamplePanel();

 assertNotNull(panel);

 //This returns a list of all the JLists
 //that are contained within the panel
 List lists = UiTestUtils.findAllInstances(JList.class, panel);
 assertEquals(1, lists.size()); //We should only have one list

 //The list should have 3 elements
 assertEquals(3, lists.get(0).getModel().getSize());

 JButton clearButton = UiTestUtils.findButtonByText("Clear", JButton.class, panel);
 assertNotNull(clearButton); //There should be a clear button in the panel

 clearButton.doClick(); //this is the method that simulates clicking the button

 //The list should have 0 elements
 assertEquals(0, lists.get(0).getModel().getSize());
 }

}

Now you can see that I am using the iterative property of swing widgets to search through the children and locate the interesting widgets, and then I can simulate interaction with them.  As you can see this test case will cover 100% of my GUI class, and it was extremely easy to do.  Also my test does not rely on ‘volatile criteria’ such as widget placement to test the functionality.  This allows me to make aesthetic  changes to the class without breaking my unit tests.  Happy Testing.

File Download: UITestUtils.java