About Me

My photo
I'm project manager of a software development team at www.researchspace.com. I've been developing bioinformatics software for the last 9 years or so, and I'm hoping to share some of the experience and knowledge I've gained with Eclipse RCP, Java web app development and software development in general on these blogs.

Saturday 27 September 2008

Testing Selection Actions in GEF (1)

Unit testing is pretty much an essential part of most Java development these days, and tools such as JUnit and their IDE integration make it much easier to get started.

It's always easier to test code that you've written yourself or have control over, but slightly less so when your code interacts with a large framework such as GEF, JFace or Draw2d that involve graphical operations. If the class you want to test has a number of superclasses, often some superclass will call a method that assumes the Eclipse platform is running or that there is a graphical display initialized.

This is the case with testing GEF classes. My solution to these issues is to use a combination of mock objects and stubs to insulate the class under test. In this blog I'm going to carry on from where I left off in my previous blog Selection Actions in GEF and describe my approach to writing unit tests involving edit parts and selection actions. Although it may seem an effort to set up, it is a 'one-time operation' that enables rapid testing of subsequent classes.

We'll start off by writing tests for the calculateEnabled() method of SrcSelectAction.
To begin with, download the Shapes Example plugin and import it into your workspace as a plugin project.
In order to mock objects, we'll use the JMock library. Download the library, add the jars into the shapes plugin project and add it to the build path. I'm using version 2.2.0 but download the latest version. We will only use mock objects in a trivial way in this example but there are excellent tutorials on the JMock website if you want to discover more.
Next, create a new source folder called 'test' and in it create the package

org.eclipse.gef.examples.actions

Now we must remember to add JUnit 4 to your project libraries , and finally we'll
add a new JUnit test case 'SrcSelectAction' into the new package.

You should end up with a project structure like this:



Setting up the test class

First of all we will create a mock IWorkbenchPart that is needed for the constructor of a selection action. In JMock, we need to add the following lines:


/*
* Tells test executor to use JMock to run this test case.
*/
@RunWith (JMock.class)
public class SrcSelectActionTest {
SrcSelectAction actionAPI; // reference to action class we're testing

Mockery mockery = new JUnit4Mockery(); // initialise the mock object system
/*
* Create a mocked IWorkbenchPart
*/
final IWorkbenchPart part = mockery.mock(IWorkbenchPart.class);


Now, we need to find a way to get a list of edit parts from the getSelectedObjects() method. In application code, this calls the Eclipse selection service which returns the current selection. Since this is a unit test none of this is available, so we'll create a Test Specific subclass of our action which will override getSelectedObjects(). We could hard code a list to be returned, but as we may want to run tests with different selections ,we will add a private method setSelectedEditParts

private class SrcSelectActionTss extends SrcSelectAction {
private List selectedObjects;
public SrcSelectActionTss(IWorkbenchPart part) {
super(part);
}
private void setSelectedObjects (List selectedObjects) {
this.selectedObjects=selectedObjects;
}
protected List getSelectedObjects (){
return selectedObjects;
}

}

Now, in the test set up we'll create the action. It is good practice when using test-specific subclasses to have two references to the action class- one defined as a
a test-specific subclass and one defined as the class we're testing (actionAPI). This makes it easier to ensure that we only test methods in the real action class and don not start testing the behaviour of the test specific subclass!

SrcSelectActionTss actionTss;
SrcSelectAction actionAPI;
@Before
public void setUp() throws Exception {
actionTss = new SrcSelectActionTss(part);
actionAPI =actionTss;
}


Writing the tests

Finally we're ready to crank out some tests!!


@Test
public void actionEnabledForSingleSelectedConection () {
//set up model and edit part
ConnectionEditPart connEP = new ConnectionEditPart();
connEP.setModel(new Connection(new RectangularShape(), new RectangularShape()));
// here we set in the selected objects as a single connection
actionTss.setSelectedObjects(Arrays.asList( new GraphicalEditPart[]{connEP}));
// assert is Enabled
assertTrue(actionAPI.calculateEnabled());
}


With a 'real' model we would probably have them defined as interfaces and could probably remove the dependency of the test on creating model objects for the edit part.
Now that we're set up though we can churn out some tests pretty fast with little effort. E.g.,


@Test
public void actionNotEnabledIfNothingSelected () {
actionTss.setSelectedObjects(Collections.EMPTY_LIST);
assertFalse(actionAPI.calculateEnabled());
}


Conclusion

Here is the final full listing of the SrcSelectActionTest :

package org.eclipse.gef.examples.actions;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.examples.shapes.model.Connection;
import org.eclipse.gef.examples.shapes.model.RectangularShape;
import org.eclipse.gef.examples.shapes.parts.ConnectionEditPart;
import org.eclipse.ui.IWorkbenchPart;
import org.jmock.Mockery;
import org.jmock.integration.junit4.JMock;
import org.jmock.integration.junit4.JUnit4Mockery;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

/*
* Tells test executor to use JMock to run this test case.
*/
@RunWith (JMock.class)
public class SrcSelectActionTest {
SrcSelectAction actionAPI; // reference to action class we're testing

Mockery mockery = new JUnit4Mockery(); // initialise the mock object system
/*
* Create a mocked IWorkbenchPart
*/
final IWorkbenchPart part = mockery.mock(IWorkbenchPart.class);

SrcSelectActionTss actionTss; // reference to TSS
private class SrcSelectActionTss extends SrcSelectAction {
private List selectedObjects;
public SrcSelectActionTss(IWorkbenchPart part) {
super(part);
// TODO Auto-generated constructor stub
}
private void setSelectedObjects (List selectedObjects) {
this.selectedObjects=selectedObjects;
}
protected List getSelectedObjects (){
return selectedObjects;
}

}

@Before
public void setUp() throws Exception {
actionTss = new SrcSelectActionTss(part);
actionAPI =actionTss;
}

@Test
public void actionEnabledForSingleSelectedConection () {
//set up model and edit part
ConnectionEditPart connEP = new ConnectionEditPart();
connEP.setModel(new Connection(new RectangularShape(), new RectangularShape()));

actionTss.setSelectedObjects(Arrays.asList( new GraphicalEditPart[]{connEP}));
assertTrue(actionAPI.calculateEnabled());
}

@Test
public void actionNotEnabledIfNothingSelected () {
actionTss.setSelectedObjects(Collections.EMPTY_LIST);
assertFalse(actionAPI.calculateEnabled());
}
@After
public void tearDown() throws Exception {
}

}



As I've hopefully demonstrated, an initial set up should reap dividends for writing unit tests for multiple actions. By using mocks and test specific subclasses, we're able to write specific, clear tests for the behaviour of the class without a large set up of the GEF framework. The test specific subclass defined here could be reused multiple times for other selection actions, if we made it a regular class rather than an inner class.

We've found JMock invaluable in our project, for mocking Eclipse components that might be unavailable in a JUnit environment.

In my next blog I'll cover how to test some action run() methods. These can require a little more set up but again the work we put in can be reused in multiple test cases.

The book X-Unit Test Patterns is a great source of ideas for improving one's test code.

Thanks for reading!

No comments: