Chapter 13. Viewing Results

A dialog is too intrusive to use as an interface for displaying test results. Over time we'd like to integrate the tests more thoroughly into the flow of programming, much as the compiler has gone from being a separate tool to becoming air—something vital that's always around. A step in the right direction is to display test results in a view. If we run a test successfully, the view should turn green. If a test fails, the view should turn red.

In this chapter we'll see

  • How to add a view extension

  • How to put a widget in a view

  • How to change the widget's color based on the updates it receives

Contributing a View

We want to add a view that will change colors based on the results of running tests. The views in Eclipse live in pages of the workbench window, as shown in Figure 13.1.

Views Live in Pages

Figure 13.1. Views Live in Pages

This picture shows that the workbench window has several pages, only one of which is active at a time. Each page contains views. Shown here are some of the views in the Java perspective. We'll need to get our view installed in this list.

Views are extensions of org.eclipse.ui.views. We can look at ContentOutline, for example:

Example . org.eclipse.ui/plugin.xml

org.eclipse.ui/plugin.xml
<extension
  point="org.eclipse.ui.views">
  ...
  <view
   name="%Views.ContentOutline"
   icon="icons/full/cview16/outline_co.gif"
   category="org.eclipse.ui"
   class="org.eclipse.ui.views.contentoutline.ContentOutline"
   id="org.eclipse.ui.views.ContentOutline">
  </view>
  ...
</extension>

In a pattern that is becoming familiar, the view extension point has a collection of elements, each of which declares a view class to use when creating the view. The icon and category are used to present the view in Windows > Show View. We won't bother with this for our example.

Since we are test-driving our code when possible, the next thing we have to do is figure out how to test our new view. To start we create a new test case ViewTest in our test plug-in org.eclipse.contribution.junit.test. Since we reference classes from org.eclipse.ui we add an import of this plug-in to the test plug-in's manifest and also update the project's build class path.

For the moment, it will be a step forward just to make the view appear. We can test this by asking the active page to display a view whose id is org.eclipse.contribution.junit.resultsView. First we need a utility method which will return the current active page:

Example . org.eclipse.contribution.junit.test/ViewTest

org.eclipse.contribution.junit.test/ViewTest
private IWorkbenchPage getPage() {
  IWorkbench workbench= PlatformUI.getWorkbench();
  IWorkbenchWindow window= workbench.getActiveWorkbenchWindow();
  return window.getActivePage();
}

PlatformUI is a Façade[1] for accessing the Eclipse user interface. We use it to get at the workbench.

Using this, we can write our test. Tests should leave the world in exactly the state they found it. We're careful to close the view before leaving the test:

Example . org.eclipse.contribution.junit.test/ViewTest

org.eclipse.contribution.junit.test/ViewTest
public void testShowHide() throws PartInitException {
  IViewPart view= getPage().showView(
    "org.eclipse.contribution.junit.resultView"
  );
  getPage().hideView(view);
}

This is an odd kind of test, containing no assertions. We often start with such tests when we're taking baby steps. They always grow some assertions. Even without assertions, running the test gives us feedback—Eclipse can't create the view (probably because we haven't implemented it). We use the common JUnit practice of just propagating exceptions—in this case, the PartInitException.

org.eclipse.ui.PartInitException: Could not create view: org.eclipse.contribution.junit
org.eclipse.contribution.junit.test/ViewTest.testView

Copying the structure of our example, we next declare our view as an extension:

Example . org.eclipse.contribution.junit/plugin.xml

<extension
  point="org.eclipse.ui.views">
  <view
   id="org.eclipse.contribution.junit.resultView"
   name="Contributed Result View"
   class="org.eclipse.contribution.junit.ResultView">
  </view>
</extension>

Running the test we get a new error message:

org.eclipse.ui.PartInitException[0]: org.eclipse.core.runtime.
CoreException[2]: java.lang.ClassNotFoundException: org.eclipse.
contribution.junit.ResultView

Looking for extenders of org.eclipse.ui.views, we find, for example, ContentOutline. ContentOutline is a subclass of ViewPart, as are most implementors of IViewPart (check this with Navigate > Open Type In Hierarchy…). We will subclass ResultView from ViewPart also.

The class creation wizard is good enough to provide stubs for the abstract superclass methods:

Example . org.eclipse.contribution.junit/ResultView

public class ResultView extends ViewPart {
  public void createPartControl(Composite parent) {
  }

  public void setFocus() {
  }
}

And our test passes. By running a run-time workbench, we can see our view. Figure 13.2 shows how to display our new view.

org.eclipse.contribution.junit/ResultView

The result (Figure 13.3) is a view with no contents.

org.eclipse.contribution.junit/ResultView

Listening to Testing Progress

Now we want the view to turn colors when tests run. If all tests run successfully, the view should turn green. If a test fails, it should turn red. We can divide the work to get this functionality running into two parts:

  1. Setting up an ITestRunListener

  2. Responding to the test callbacks by changing the color

How do we want to set up a listenerOne way would be to extend the test listener extension point we declared towards the end of Circle One. However, this would mean that our view would have to handle test callbacks whether it was open or not. Using a dynamic listener better matches the life cycle of the view because the view can be opened and closed.

We first tested for this by checking to make sure a listener with a certain class was on the list of listeners. This seemed to us a bit too tied to the implementation. A simpler testing strategy is to just count the listeners. When the view opens there should be one more listener than before, and after the view is closed there should be the same number as at the start. First we introduce a couple of helper methods to make the tests read better and we also store the view in an instance variable:

Example . org.eclipse.contribution.junit.test/ViewTest

org.eclipse.contribution.junit.test/ViewTest
private ResultView view;

public void showView() throws PartInitException {
  view= (ResultView) getPage().showView(
       "org.eclipse.contribution.junit.resultView"
  );
}
public void hideView() {
  getPage().hideView(view);
}

This in place, we can count listeners:

Example . org.eclipse.contribution.junit.test/ViewTest

org.eclipse.contribution.junit.test/ViewTest
public void testViewListener() throws PartInitException {
  int count= JUnitPlugin.getPlugin().getListeners().size();
  showView();
  try {
    assertEquals(count + 1,
      JUnitPlugin.getPlugin().getListeners().size());
  } finally {
    hideView();
  }
  assertEquals(count,
    JUnitPlugin.getPlugin().getListeners().size());
}

Now we need to make the test work. First, we need a stub listener inner class. It can be private, because no one else needs to use it:

Example . org.eclipse.contribution.junit/ResultView$Listener

private class Listener implements ITestRunListener {
  public void testsStarted(int testCount) {
  }
  public void testsFinished() {
  }
  public void testStarted(String klass, String method) {
  }
  public void testFailed(String klass,String method,String trace) {
  }
}

We've already seen createPartControl(), the life-cycle method used to open a view. We don't have to create any widgets yet, but we do need to hook up a listener:

Example . org.eclipse.contribution.junit/ResultView

private ITestRunListener listener;
public void createPartControl(Composite parent) {
  listener= new Listener();
  JUnitPlugin.getPlugin().addTestListener(listener);
}

We'll need to override a life-cycle method we haven't seen before, dispose(), to unhook the listener when the view is hidden. We need to be careful unhooking the listener, because it's possible for dispose() to be called without createPartControl() being called first. CreatePartControl() is called as lazily as possible. It is only called when the view actually has become visible. However, dispose is always called independent of the view's visibility status. This is an asymmetry in the API life cycle (there is no disposePartControl()). This asymmetry in the API can easily result in NullPointerExceptions. Because we are using the presence of an object in the field listener as a flag, we need to make sure we clear the field after we've unhooked the listener.

Example . org.eclipse.contribution.junit/ResultView

public void dispose() {
  if (listener != null)
    JUnitPlugin.getPlugin().removeTestListener(listener);
  listener= null;
}

The test passes. We now have a view that registers and unregisters a test listener. Next we need to respond to the test callbacks.

Changing Colors

We need to change the color of the view based on the results of running tests. First, how are we going to test? The form of these tests will be to open a view, run a test, and close the view. Since we don't need to do anything else when we are opening or closing the test, we can copy our previous test class to a new test class, ViewColorTest and we delete the existing test methods. We can then use JUnit's setUp() and tearDown() methods to guarantee that we close the view once it's been opened:

Example . org.eclipse.contribution.junit.test/ViewColorTest

org.eclipse.contribution.junit.test/ViewColorTest
public void setUp() throws PartInitException {
  view= (ResultView) getPage().showView(
     "org.eclipse.contribution.junit.resultView"
  );
}

public void tearDown() {
  getPage().hideView(view);
}

It may seem a bit strange to devote a whole new testing class to this small change. We find that if we have the slightest difference in setup, it's worth it to have a new test class.

We'll work backwards to our first test, the one that makes sure that if the tests pass the view turns green. To test the color, we need access to the view's control. Org.eclipse.swt.widgets.Control represents an atomic widget on the screen. It is the abstract class in which the method getBackground() is defined.

The assertion will compare the actual color of our control to the expected color:

Example . org.eclipse.contribution.junit.test/ViewColorTest

org.eclipse.contribution.junit.test/ViewColorTest
public void testResultViewGreen() throws PartInitException {
  // Run tests
  Display display= view.getControl().getDisplay();
  Color green= display.getSystemColor(SWT.COLOR_GREEN);
  assertEquals(green, view.getControl().getBackground());
}

We want to create the effect of having successfully run a test inside testResultViewGreen() without actually running a test, which would require more setup. We can simulate the broadcast messages we expect to be generated by a correctly running test—starting and ending the tests. We simulate test running by calling the ITestRunListener methods directly:

Example . org.eclipse.contribution.junit.test/ViewColorTest

org.eclipse.contribution.junit.test/ViewColorTest
public void testResultViewGreen() throws PartInitException {
  view.getListener().testsStarted(0);
  view.getListener().testsFinished();
  Display display= view.getControl().getDisplay();
  Color green= display.getSystemColor(SWT.COLOR_GREEN);
  assertEquals(green, view.getControl().getBackground());
}

To make the test compile, we need to provide access to both a control and the listeners of the ResultView. We use Source > Generate Getters and Setters... to create the getters.

Example . org.eclipse.contribution.junit/ResultView

private ITestRunListener listener;
private Control control;

public Control getControl() {
  return control;
}
public ITestRunListener getListener() {
  return listener;
}

The simplest control is a Label. We can initialize it in createPartControl():

Example . org.eclipse.contribution.junit/ResultView

public void createPartControl(Composite parent) {
  listener= new Listener();
  JUnitPlugin.getPlugin().addTestListener(listener);
  control= new Label(parent, SWT.NONE);
}

For now, it suffices to set the background color of the control to green when the tests finished. (We'll test for red on failure in a moment.) We get access to a resource representing green exactly the same way we did in the test. It's common for work done writing the test to come in handy when the time comes to make the test pass.

Example . org.eclipse.contribution.junit/ResultView

public void testsFinished() {
  Display display= view.getControl().getDisplay();
  Color green= display.getSystemColor(SWT.COLOR_GREEN);
  control.setBackground(green);
}

The test for red is similar to the test for green. We simulate the failure of a test, then check to make sure the view is red. After the tests finish, the view should still be red. Since the actual class and method of the failing test don't matter at the moment, we can just fill in bogus values.

Example . org.eclipse.contribution.junit.test/ViewColorTest

org.eclipse.contribution.junit.test/ViewColorTest
public void testResultViewRed() throws PartInitException {
  view.getListener().testsStarted(0);
  view.getListener().testFailed("class", "method", "trace");
  Display display= view.getControl().getDisplay();
  Color red= display.getSystemColor(SWT.COLOR_RED);
  assertEquals(red, view.getControl().getBackground());
  view.getListener().testsFinished();
  assertEquals(red, view.getControl().getBackground());
}

To make this work, we need to set a flag in the listener when the tests start running. If a test ever fails, the view's color is set to red. When the tests finish, the view is only set to green if all the tests succeeded:

Example . org.eclipse.contribution.junit/ResultView$Listener

private boolean success;

public void testsStarted(int testCount) {
  success= true;
}

public void testsFinished() {
  if (success) {
   Display display= control.getDisplay();
   Color green= display.getSystemColor(SWT.COLOR_GREEN);
   control.setBackground(green);
  }
}

public void testFailed(String klass, String method, String trace) {
  Color red= control.getDisplay().getSystemColor(SWT.COLOR_RED);
  control.setBackground(red);
  success= false;
}

And the tests, both red and green, succeed. Now we have a view that turns green and red depending on the results of running a test. You may want to take a moment to start up a run-time workbench, show the view, and run a couple of tests to verify that the colors are correct. Over time we'd like our test suite to demonstrate satisfaction—if the tests all run we're perfectly satisfied with the behavior of our current functionality. The tests will never be perfect predictors, but over time we can hope to make them good enough that we can act like they are, with only occasional unpleasant surprises.

We'll clean-up one item before we move on. It is easy to forget even though Eclipse generates a To Do reminder for us. The class creation wizard created setFocus() as follows:

Example . org.eclipse.contribution.junit/ResultView

public void setFocus() {
  //TODO Auto-generated method stub
}

Let's check the API specification for this method. When setFocus() is selected, we can select the method name and with Open Super Implementation from the context menu we can see the definition in IWorkbenchPart.

Example . org.eclipse.ui/IWorkbenchPart

org.eclipse.ui/IWorkbenchPart
/**
 * Asks this part to take focus within the workbench.
 * <p>
 * Clients should not call this method (the workbench
 * calls this method at appropriate times).
 * </p>
 */

Our ResultView doesn't take focus yet. To fix this we just tell the Label created in createPartControl() to take the focus:

Example . org.eclipse.contribution.junit/ResultView

public void setFocus() {
  control.setFocus();
}

It is good Eclipse practice to always check the API contract of methods you implement. This leads us to the Program to the API Contract rule:

  • PROGRAM TO API CONTRACT RULECheck and program to the Eclipse API contract.

The next feature we'll add to our user interface is a menu in the view. Before starting on that, we'll review. In this chapter we

  • Extended the org.eclipse.ui.views extension point

  • Wrote a test to show and hide the view

  • Implemented a new IViewPart

  • Tested and implemented a test listener tied to the life-cycle methods of our view

  • Tested and implemented changing the view's color in response to test runner broadcast messages



[1] See E. Gamma, R. Helm, R. Johnson, and J. Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, Reading, MA, 1995.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset