Agile User Interface Development

Overview

"If you're not doing Agile, you're in the past." This is the message
of the recent SD Best Practices 2004 conference. Agile processes like XP and
Scrum are becoming pervasive in the world of software development. Agile is
a sea change, refocusing software developers on quality and speed. Its impact
on the practice of software development is already being compared to that of
object-oriented design. However, one area of effort has been slow to change:
development of the graphical user interface (GUI). Since most software includes
some type of GUI, and a good percentage of software development is completely
GUI-centric, applying the advantages of Agile to GUI building is of key importance.

What is preventing people from building GUIs in an Agile way? Whether their
application is web-based or a desktop application, most developers don't do
test-driven development (TDD) of the user interface. This is for a simple reason:
unit testing GUI software is hard. Tests that exercise the GUI can be tedious
and error-prone, involving complex code to simulate user events, wait while
events propagate and controls redraw, and then attempt to check the state as
it would appear to the user. Agility depends on doing TDD, but effective tests
of specific behaviors are difficult to write for the GUI. The quality and design
benefits of Agile have yet to be fully realized on the GUI side of the cube
farm.

Agile practices are creeping into this domain. Tools for unit testing GUI
elements are proliferating. The JFCUnit framework tests GUIs built using Java
Swing. Web-based GUIs can be tested with HTMLUnit, HTTPUnit, jWebUnit, and
similar tools. Many GUI builders and toolkits have associated unit testing
tools, such as VBUnit for Visual Basic and QtUnit for Qt.

The tools exist, but the process is still emergent. In TDD, each code change
is preceded by a unit test of the new behavior. In GUI development, many changes
are just tweaks to the visual appearance, such as changing element positions,
text, or color. You might add a button, create a menu item, or construct a
dialog. But how and why would you test these kinds of changes? Testing every
label or color value would be insane. Likewise, for standard elements like
buttons and fields, it's pointless to test their generic behaviors, such as
responding to mouse movements, key presses, clicks, and so forth. They are
not likely to break. Questions of what to test just increase the innate difficulty
of building GUI tests.

The critical question: how do you do test-first GUI development? The answer
lies in how the GUI code is structured. Agile gurus such as Kent Beck and David
Astels suggest building the GUI by keeping the view objects very thin, and
testing the layers "below the surface." This "smart object/thin
view" model is analogous to the familiar document-view and client-server
paradigms, but applies to the development of individual GUI elements. Separation
of the content and presentation improves the design of the code, making it
more modular and testable. Each component of the user interface is implemented
as a smart object, containing the application behavior that should be tested,
but no GUI presentation code. Each smart object has a corresponding thin view
class containing only generic GUI behavior. With this design model, GUI building
becomes amenable to TDD.

Example: Building a Login Dialog

Let's walk through an example of how to develop a GUI dialog using TDD and
the smart object/thin view code design model. First, let's consider the graphic
design of the dialog. Agile development calls for minimal up-front design,
letting the software architecture evolve through multiple development cycles,
but this approach isn't a good idea for GUI design. Designing a user interface
is a creative process that should be approached formally, with sketches, prototyping,
and usability testing. So, although the code behind the GUI can be designed
iteratively using TDD, a sketch of the visual design is a smart first step.
The basic design for the login dialog is sketched in Figure 1.

Figure 1. GUI design sketch for login dialog

The dialog is simple, containing user name and password fields, corresponding
static text labels, and Login and Cancel buttons. As
an initial outline of its behavior, let's decide that a successful login causes
the dialog to close, but it remains open in case of login failure. The Cancel button
also closes the dialog.

The basic smart object/thin view class design for the code implementing the
dialog is shown in Figure 2.

Figure 2. The classes LoginDialog and LoginDialogView

The smart object class LoginDialog will contain a method corresponding to
each functional behavior of the dialog. The thin view class LoginDialogView
will only contain simple display-related code, and get/set methods to read
or set the displayed information. With this approach, only the complex functionality
in LoginDialog needs to be unit tested. We can be pretty confident that the
simple behavior in LoginDialogView will work.

The first component to build is the smart object LoginDialog. It needs a corresponding
test class LoginDialogTest. The first test method will verify the login method,
as shown in Figure 3.

As the test-first development process dictates, the unit test is written first.
The test anticipates and defines the design of the functionality being tested.
We need to take a user name and password, and return a login success or failure.
A sensible interface to do this is:

boolean login(String username, String password);

The test class LoginDialogTest will test this function. Example 1 shows its
initial implementation in the file LoginDialogTest.java.

This test builds on the JUnit base test class TestCase. The test method testLogin()
creates an instance of LoginDialog, calls its login() method, and asserts that
the result is true. This code will not compile, since LoginDialog doesn't exist.
Following the TDD process, LoginDialog should be stubbed, the code compiled,
and the test run to verify that it fails as expected. Then, LoginDialog is
given the minimum implementation to pass the unit test, following the Agile
mantra of doing "the simplest thing that could possibly work." Example
2 shows the initial version of LoginDialog with the minimum code to pass the
unit test, implemented in the file LoginDialog.java.

The classpath must include junit.jar to build the unit test,
since it uses JUnit. On Linux, Mac OS X, and other UNIX systems, the classpath should
include a colon (:) rather than a semicolon as shown above.

The test is run as follows:

java -classpath ".;junit.jar" junit.textui.TestRunner LoginDialogTest

The unit test passes, hurrah! Unfortunately, the code is bogus. The login()
method will always approve the login. No doubt, the customer will not appreciate
this level of security. Clearly, the next test to write is one that verifies
the login will fail if incorrect credentials are given. Example 3 shows LoginDialogTest
with a second test method to fulfill this goal, testLoginFail(). Since both
tests use an instance of LoginDialog, the test class is refactored as a test
fixture that creates the LoginDialog in its setUp() method.

LoginDialog must be made to pass the new test, without failing the first test.
The TDD process leads us to build the real functionality we needed, in which
the login succeeds if the user name and password are correct, and fails otherwise.
Example 4 shows LoginDialog with these changes.

LoginDialog now passes both tests. To do so, it contains user name and password
fields, which must be matched for the login to succeed. Obviously, this is
only slightly better than the first version in terms of security. The login
code should not contain hard-coded values for authentication! At this point,
we could introduce a separate class to contain and authenticate users' login
information, which LoginDialog will use. However, this example is about building
the GUI, so let's leave the unsafe login code in place and move on.

At this point, we've built the login functionality, and have it covered by
unit tests, but have no visible GUI to show for it. What should be done next?
With the actual functionality already done and tested, all that has to be done
on the GUI side is to create and display the graphical elements, and to call
the login() method at the appropriate time. This functionality is generic and
can be built simply, so that it doesn't contain complex behavior that could
break and would require unit testing. Thus, when building the GUI element,
we don't need to do test-first development. Example 5 shows the code for the
Swing class LoginDialogView that creates the dialog window, implemented in
the file LoginDialogView.java.

LoginDialogView contains the text field, label, and button elements. Aside
from generic GUI behavior, it only has one simple behavior, implemented by
the actionPerformed() method. This behavior is that, when the Login button
is clicked, the login() method is called. If the login succeeds, the dialog
is closed by calling its hide() method.

In order to call the login() function, LoginDialogView needs an instance of
LoginDialog, which it receives in its constructor. Otherwise, it consists entirely
of GUI-setup and event-handling code. The majority of its code is in addControls(),
which simply creates and arranges the GUI elements on the window.

The code for LoginDialogView demonstrates how a GUI thin view element can
be designed so that it only contains generic GUI code, and the important application
behavior requiring testing resides in a separate, testable smart object. LoginDialogView
need only be tested by creating it, looking at it, and making sure it looks
and works as expected from the user perspective. Example 6 shows the executable
class AppMain that creates the dialog window for hands-on usability testing.

The class AppMain simply creates a LoginDialog and LoginDialogView, shows
the view, sleeps until the view is closed, and then exits.

AppMain is run as shown here:

java –classpath "." AppMain

Running it creates the login dialog window, as shown in Figure 4.

Figure 4. The login dialog window

Interacting with the login dialog verifies that clicking Login with
the values shown in Figure 4 causes the login to succeed and the window to
close. Trying to log in with other values leaves the window open, since the
login has failed. The Cancel button closes the window, as does
the window close button. The login dialog works as designed.

Conclusions

We've created a login dialog following TDD and a smart object/thin
view design model. The result is well-architected and functional. The functional
application behavior is covered by unit tests, and the generic display code
doesn't require complex GUI tests. Figure 5 shows the software architecture
we've developed.

At this point, additional features can be added. The login dialog could have
a message field to alert the user when the login has failed. Fields for additional
login parameters can be added. A separate authentication object can be created
and the hard-coded login values removed. Regardless of the changes to be made,
TDD and the smart object/thin view model provide a clear direction for their
design and implementation. Important application functionality resides in the
smart object, where it can be tested, and generic display code resides in the
thin view.

For more detailed examples of test-driven GUI development, and extensive coverage
of JUnit and other xUnit test frameworks, TDD, and unit test strategies, see
my book Unit Test Frameworks, published in November 2004 by O'Reilly
Press.

Paul Hamill is a highly experienced software developer with more than ten years of experience developing code using C/C++, Java, and other languages. He is most recently the author of "Unit Test Frameworks."