For the last year or two I’ve been developing desktop applications with the Eclipse Rich Client Platform (RCP). One of the most difficult aspects of this sort of development has been testing. GUI testing can be tricky and there’s lots of reasonable reasons why someone would avoid GUI testing as much as possible. However, seeing how the test driven development (TDD) approach has helped me in other areas, I’ve wanted to discover a good TDD process for Eclipse RCP development.
After spending time reading up on GUI design patterns I felt I had a reasonable grasp on several architectural approaches, but not on a preferred development process. This week Tim Ottinger pointed me to presenter first and I think it may be the final key to getting everything just right. Atomic Object’s papers on the pattern are decent but I’ve but together another example to see the process step by step.
Imagine a simple screen with two lists and two buttons, one for moving items from list A to list B and the other button for movement of B to A. List A constains “Good, Fast, Cheap, Done” and the constraint is that you can only pick 3. Once you do, the A to B button must be disabled. Using a test driven approach, development go like this:
We create 4 objects:
IProjectModel <-- the model interface
IProjectView <-- the view interface
ProjectPresenter <-- presenter that takes view and model in constructor
Option <-- the model data object (wraps a string)
Then we start creating tests. Our first test looks like this:
public void setUp() {
good = new Option("good");
cheap = new Option("cheap");
fast = new Option("fast");
done = new Option("done");
List options = new ArrayList();
options.add(good);
options.add(cheap);
options.add(fast);
options.add(done);
model = new MockProjectModel( options );
view = new MockProjectView();
presenter = new ProjectPresenter(model,view);
}
public void testModelAndViewInitialization() {
int viewOptions = view.getAvailableOptions().size();
int modelOptions = model.getAvailableOptions().size();
assertTrue( viewOptions == modelOptions );
}
Okay, so what are we testing here? We’re testing if the presenter has initialized the view. That is, the only way the view will have the same available options is if the presenter does the wiring. However, if you actually look at our test code, the presenter only appears once, and that is in the setup. So for this test to pass, we go into our presenter:
// presenter constructor
public ProjectPresenter( IProjectModel model, IProjectView view ) {
this.model = model;
this.view = view;
initializeView();
}
private void initializeView() {
view.setAvailableOptions( model.getAvailableOptions );
}
Now, notice what has happened. We’ve added methods to the model and view in the test and in the presenter. In this case, we’re using hand mocked objects so the methods we added in the test can be added directly to the mocks (not to the interface). In the case of the presenter we only have access to the interfaces so those two methods (view.setAvailableOptions and model.getAvailableOptions) must be added to the interface. In this way we naturally determine what methods should be in the interfaces.
We implicitly test the presenter by running methods and assertions on the view and model. Ultimately the model and the view are what’s most important. At the same time, during testing we have only mock views and mock models. That’s okay because our purpose is to experientially determine the proper model and view interfaces. Once we have a fairly complete set of tests we can then go off with our interfaces and implement the real view and model. Our model can easily be tested itself and our view is very thin, a passive view in Fowler’s terms. Moreover, we’ll only be implementing what we know we need from testing.
Let’s look at one more test as an example:
public void testChooseAvailableOption() {
assertTrue( model.getSelectedOptions().size() == 0 );
view.selectOption( good );
assertTrue( model.getSelectedOptions().size() == 1 );
assertTrue( view.isAddButtonEnabled() );
}
Again, our test is describing real functionality. It’s becoming a specification. That is, if you select an option that option should now be selected in the model and the view’s add (A to B) button should remain enabled. To implement this, we’ll need to have the view delegate all events to the presenter.
In the end the presenter is just a bunch of wiring. But that’s okay. Our real goal here isn’t to have a smart presenter. Our real goal is twofold: (1) to have a testable user interface and (2) to separate the model and the view. The presenter is merely a necessary evil for the second. And by going the TDD route, we get goal one by definition.
I’ve put together a working example with source code as an Eclipse plugin. You should be able to import this plugin (File -> Import Plugins and Fragments) into your Ecplise workspace and run the tests. If your drop the plugin directly into your Eclipse plugins directory you’ll find the example available under the “MPV Examples” view.
Commentary