Hunting for an SWT Test Framework? Say Hello to Red Deer

This is the first in a series of posts on the new “Red Deer” (https://github.com/jboss-reddeer/reddeer) open source testing framework for Eclipse. In this post, we’ll introduce Red Deer, and take a look at the some of the advantages that it offers by building a sample test program from scratch.

Some of the features that Red Deer automated offers are: 

Note that as of this writing, Red Deer is in an incubation stage. The current release is at level 0.5. The target date for the 1.0 release of Red Deer is late 2014. But, as a community-based, open source project, now is a great time to try Red Deer and make suggestions or even contribute code!

A Look at Red Deer’s Architecture

The Red Deer project itself is comprised of utilities and the API that supports the development and execution of automated tests.

The API (the parts of the above diagram that are enclosed in dashed line boxes) can be thought of as having three layers:

What Makes Red Deer different from other Tools? A Layer of Abstraction

The top-most layer of the API enables you to instantiate Eclipse UI elements as objects, and then manipulate them through their methods. The resulting code is easier to read and maintain, instead of being brittle and subject to failures when the UI changes.

For example, for a test that has to open a view and press a button, without Red Deer, the test would have to navigate the top level menu, find the view menu, then the view type in that menu, then find the view open dialog, then locate the “OK” button, etc. Your test would have to spend a lot of time navigating through the UI elements before it could even begin to perform the test’s steps.

With Red Deer, the code to open a view (in this case, the servers view) is simply:

ServersView view = new ServersView();
view.open();

Furthermore, within that ServersView, your test program can perform operations on the View through methods which are defined in the view (and are incidentally also well debugged by the Red Deer team), instead of having to explicitly locate and manipulate the UI elements directly. For example, to obtain a list of all the servers, instead of locating the UI tree that contains the server list, and extracting that list of servers into an array, your Red Deer program can simply call the “getServers()” method.

Likewise, the code to open a PackageExplorer, and then select a project within that PackageExplorer is as follows:

PackageExplorer packageExplorer = new PackageExplorer();
packageExplorer.open();
packageExplorer.getProject("myTestProject").select();

And, the code to retrieve all the projects within that PackageExplorer is simply:

packageExplorer.getProjects();

The result are that your tests are easier to write and maintain and you can focus on testing your application’s logic instead of writing brittle code to navigate through the application.

Installing Red Deer

The only prerequisites to using Red Deer are Eclipse and Java. In this post, we’ll use Eclipse Kepler and OpenJDK 1.7, running on Red Hat Enterprise Linux (RHEL) 6.

To install Red Deer 0.4 (this is the latest stable milestone version as of this writing) follow these steps:

Now that you have Red Deer installed, let’s move onto building a new Red Deer test.

Building your First Red Deer Test

To create a new Red Deer test project, you make use of the Red Deer UI tooling and select New->Project->Other->Red Deer Test:

Before we move on, let’s take a look at the WEB-INF/MANIFEST.MF file that is created in the project:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: com.example.reddeer.sample
Bundle-SymbolicName: com.example.reddeer.sample;singleton:=true
Bundle-Version: 1.0.0.qualifier
Bundle-ActivationPolicy: lazy
Bundle-Vendor: Sample Co
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Require-Bundle: org.junit, org.jboss.reddeer.junit, org.jboss.reddeer.swt, org.jboss.reddeer.eclipse

The line we’re interested in is the final line in the file. These are the bundles that are required by Red Deer.

After the empty project is created by the wizard, you can define a package and create a test class.  Here's the code for a minimal functional test. The test will verify that the eclipse configuration is not empty.

package com.example.reddeer.sample;  
  
import static org.junit.Assert.assertFalse;  
import java.util.List;  
import org.jboss.reddeer.swt.api.TreeItem;  
import org.jboss.reddeer.swt.impl.button.PushButton;  
import org.jboss.reddeer.swt.impl.menu.ShellMenu;  
import org.jboss.reddeer.swt.impl.tree.DefaultTree;  
import org.junit.Test;  
import org.junit.runner.RunWith;  
import org.jboss.reddeer.junit.runner.RedDeerSuite;  
  
@RunWith(RedDeerSuite.class)  
public class SimpleTest {  
  
   @Test  
   public void TestIt() {  
      new ShellMenu("Help", "About Eclipse Platform").select();  
      new PushButton("Installation Details").click();  
      DefaultTree ConfigTree = new DefaultTree();  
      List<TreeItem> ConfigItems = ConfigTree.getAllItems();  
  
      assertFalse ("The list is empty!", ConfigItems.isEmpty());  
      for (TreeItem item : ConfigItems) {  
          System.out.println ("Found: " + item.getText());  
      }  
   }  
}


After you save the test's source file, you can run the test.  To run the test, select the Run As->Red Deer Test option:

And - there's the green bar!

Simplifying Tests with Requirements

Red Deer requirements enable you to define actions that you want happen before a test is executed. The advantage to using requirements is that you define the actions with annotations instead of using a @BeforeClass method. The result is that your test code is easier to read and maintain. The biggest difference between a Red Deer requirement and the the @BeforeClass annotation from the JUnit framework is that if a requirement cannot be fulfilled the test is not executed.

Like everything else in Red Deer, you can make use of predefined requirements, or you can extend the feature by adding your own custom requirements. These custom requirements can be made complex and for convenience can be stored in external properties files. (We’ll take a look at defining custom requirements in a later post in this series when we examine how to create and contribute extensions to Red Deer.)

The current milestone release of Red Deer provides predefined requirements that enable you to clean out your current workspace and open a perspective. Let’s add these to our example.

To do this, we need to add these import statements:

import org.jboss.reddeer.eclipse.ui.perspectives.JavaBrowsingPerspective;
import org.jboss.reddeer.requirements.cleanworkspace.CleanWorkspaceRequirement.CleanWorkspace;
import org.jboss.reddeer.requirements.openperspective.OpenPerspectiveRequirement.OpenPerspective;

And these annotations:

@CleanWorkspace
@OpenPerspective(JavaBrowsingPerspective.class)

And, we also have to  a reference to org.jboss.reddeer.requirements to the required bundle list in our example’s MANIFEST.MF file:

Require-Bundle: org.junit, org.jboss.reddeer.junit, org.jboss.reddeer.swt, org.jboss.reddeer.eclipse, org.jboss.reddeer.requirements

When we’re done, our example looks like this:

package com.example.reddeer.sample;

import static org.junit.Assert.assertFalse;
import java.util.List;
import org.jboss.reddeer.swt.api.TreeItem;
import org.jboss.reddeer.swt.impl.button.PushButton;
import org.jboss.reddeer.swt.impl.menu.ShellMenu;
import org.jboss.reddeer.swt.impl.tree.DefaultTree;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.jboss.reddeer.junit.runner.RedDeerSuite;
import org.jboss.reddeer.eclipse.ui.perspectives.JavaBrowsingPerspective;
import org.jboss.reddeer.requirements.cleanworkspace.CleanWorkspaceRequirement.CleanWorkspace;
import org.jboss.reddeer.requirements.openperspective.OpenPerspectiveRequirement.OpenPerspective;

@RunWith(RedDeerSuite.class)
@CleanWorkspace
@OpenPerspective(JavaBrowsingPerspective.class)

public class SimpleTest {
   @Test
   public void TestIt() {
       new ShellMenu("Help", "About Eclipse Platform").select();
       new PushButton("Installation Details").click();
       DefaultTree ConfigTree = new DefaultTree();
       List<TreeItem> ConfigItems = ConfigTree.getAllItems();

       assertFalse ("The list is empty!", ConfigItems.isEmpty());
       for (TreeItem item : ConfigItems) {
           System.out.println ("Found: " + item.getText());
       }
   }
}

Notice how we were able to add those functions to the test code, while only adding a very small amount of actual new code? Yes, it can pay to be a lazy programmer. ;-) 

What’s Next?

What’s next for Red Deer is its continued development as it progresses through its incubation stage until its 1.0 release. What’s next for this series of posts will be discussions about:

Author’s Acknowledgements

I’d like to thank all the contributors to Red Deer for their vision and contributions. It’s a new project, but it is growing fast! The contributors (in alphabetic order) are: Stefan Bunciak, Radim Hopp, Jaroslav Jankovic, Lucia Jelinkova, Marian Labuda, Martin Malina, Jan Niederman, Vlado Pakan, Jiri Peterka, Andrej Podhradsky, Milos Prchlik, Radoslav Rabara, Petr Suchy, and Rastislav Wagner.

 

 

 

 

 

Top