Saturday, February 14, 2009

Doing a GUI application using TDD

Testing user interfaces is always a pain point when doing TDD GUI apps. Web development has been able to get around this a bit with automated UAT testing using tools like Selenium or Watir. But with GUI apps, there's no really good, cheap (as in free) UAT tools (if anyone knows of one, please let me know!!!).

So when I create a GUI app I use a MVP pattern where the View is really, really thin because it's just impossible to right good tests around it. In the past with Swing and SWT/RCP application, I've been able to have the view be fairly simple with an exposed getter/setter-ish API on it so that the Presenter can be tested on how it manipulates the View and the Model can have it's own tests for the business rules and that leaves not a whole bunch to test in the View. You don't really need tests for the implementat of view.getUsername() when all it does is read the text from a text field.

Here's a sample login MVP of what I'm talking about...
(I didn't compile this so don't complain if it doesn't actually work)

public class Presenter {
public MyPresenter(final IView view, final IModel model) {
view.addLoginListener(new IListener() {
public void fireEvent() {
try {
model.authenticate(view.getUsername(),
view.getPassword());
} catch (AuthenticationException e) {
view.showErrorMessage(e.getMessage());
}
}
});
}
}


The API exposed on the view is real simple:

public interface IView {
void addLoginListener(IListener listener);
String getUsername();
String getPassword();
void showErrorMessage(String errorMessage);
}


And the API on the model is real simple too:

public interface IModel {
String authenticate(String username, String password)
throws AuthenticationException;
}


So this example makes the presenter very testable. And it separates out the need to know how the stuff is displayed to the user verses and how the actual authentication needs to happen. And the model is is left with a single responsibility and a very simple API. So testing the model is now real easy also. And the view now has no complicated functionality.

Even if you wanted to have some fields in the view that enable or disable based on options that the user has selected in other parts of the view, you can create another MVP triad for that and you could have another interface for that the view implements that might have some methods that look like this...

void enableSubmitButton();
void disableSubmitButton();

So the View doesn't even know what the state of the submit button is. It just knows how to enable or disable the field. Again, all the logic can go off to a Model so that it is testable and the Presenter just acts as a mediator between the View and Model interfaces which keeps the Presenter really testable.

As a general rule of thumb, you write a test for anything that could possibly break. And at the point of where this view is at, what would you be testing? That Swing is working? You shouldn't be testing their code, just yours.

Now in my game, I'm using Java3D for my "View" so that's going to be a fairly complicated piece that needs to be split as thin as possible to provide testability. Because really that should be your goal: how can I make this code testable? If I abstract something out and hide it behind an interface and inject it in, I can test my classes better.

2 comments:

  1. Very cool. We used to struggle with that too ... Selenium is nice but it has its limitations. We actually had to build some of our own stuff. We also struggled with JavaScript and doing TFD but that has become much more common. Now that we are doing so much SharePoint work it's a real pain because Microsoft has closed so many of their classes you can't write tests classes with mock objects of the SharePoint objects. Nice. Good luck with your project.

    ReplyDelete
  2. I've heard some good things about JSUnit for doing TFD with Javascript but I'm not sure how well that works with other frameworks or with DOM objects. But if you could abstract away the DOM, you could test how your code interacts with the abstraciton.

    I've also had some issues with AJAX and Selenium. Mainly just waiting for the returns.

    But, IMHO, any way to get good (quality not quantity) code coverage tests and to automate those tests, is definitely worth some time/effort/re-design. At all times you need to know that your stuff works!

    Thanks for the comment!

    ReplyDelete