Monday, December 23, 2013

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” ( open source testing framework for Eclipse. In this post, we’ll be introduced to 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:

  • An easy to use, high-level API for testing standard Eclipse components
  • Support for creating custom extensions for your own applications
  • A requirements validation mechanism to assist you in configuring complex tests
  • Eclipse Tooling to Assist in Creating new Projects
  • A record and playback tool to enable you to quickly create automated tests
  • An integration with Selenium for testing web based applications
  • Support for running tests in a Jenkins CI environment

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:

  • The top layer consists of extensions to Red Deer’s abstract classes or implementations for Eclipse components such as Views, Editors, Wizards, or Shells. For example, if you are writing tests for a feature that uses a custom Eclipse View, you can extend Red Deer’s View class by adding support for the specific functions of the feature. The advantage that this API layer gives you is that your test programs do not have to focus on manipulating the individual UI elements directly to perform operations. Your programs can instead instantiate an instance of an Eclipse component such as a View, and then use that instance’s methods to perform operations on the View. This layer of abstraction makes your test programs easier to write, understand, and maintain.

  • The middle layer consists of the Red Deer implementations for SWT UI elements such as: Button, Combo, Label, Menu, Shell, TabItem, Table, ToolBar, Tree. This API layer supports the API’s higher level by providing the building blocks for the API’s Views, Editors, Shells, and WIzards. This middle layer of the API also provides Red Deer packages that enable your tests to enforce requirements, so that necessary setup tasks are performed before a test is run.

  • The bottom layer consists of Red Deer packages that support the execution of tests such as: Conditions, Matchers, Widgets, Workbench, and Red Deer extensions to JUnit.

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();;  

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();  

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


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 the 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 (highlighted) 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.

1:  package com.example.reddeer.sample;  
3:  import static org.junit.Assert.assertFalse;  
4:  import java.util.List;  
5:  import org.jboss.reddeer.swt.api.TreeItem;  
6:  import org.jboss.reddeer.swt.impl.button.PushButton;  
7:  import;  
8:  import org.jboss.reddeer.swt.impl.tree.DefaultTree;  
9:  import org.junit.Test;  
10:  import org.junit.runner.RunWith;  
11:  import org.jboss.reddeer.junit.runner.RedDeerSuite;  
13:  @RunWith(RedDeerSuite.class)  
14:  public class SimpleTest {  
16:    @Test  
17:    public void TestIt() {  
19:      new ShellMenu("Help", "About Eclipse Platform").select();  
20:      new PushButton("Installation Details").click();  
22:      DefaultTree ConfigTree = new DefaultTree();  
23:      List<TreeItem> ConfigItems = ConfigTree.getAllItems();  
25:      assertFalse ("The list is empty!", ConfigItems.isEmpty());  
26:      for (TreeItem item : ConfigItems) {  
27:        System.out.println ("Found: " + item.getText());  
28:      }  
29:    }  
30:  }  

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:


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 (the new lines are highlighted):

1:  package com.example.reddeer.sample;  
3:  import static org.junit.Assert.assertFalse;  
4:  import java.util.List;  
5:  import org.jboss.reddeer.swt.api.TreeItem;  
6:  import org.jboss.reddeer.swt.impl.button.PushButton;  
7:  import;  
8:  import org.jboss.reddeer.swt.impl.tree.DefaultTree;  
9:  import org.junit.Test;  
10:  import org.junit.runner.RunWith;  
11:  import org.jboss.reddeer.junit.runner.RedDeerSuite;  
12:  import org.jboss.reddeer.eclipse.ui.perspectives.JavaBrowsingPerspective;  
13:  import org.jboss.reddeer.requirements.cleanworkspace.CleanWorkspaceRequirement.CleanWorkspace;  
14:  import org.jboss.reddeer.requirements.openperspective.OpenPerspectiveRequirement.OpenPerspective;  
16:  @RunWith(RedDeerSuite.class)  
17:  @CleanWorkspace  
18:  @OpenPerspective(JavaBrowsingPerspective.class)  
19:  public class SimpleTest {  
21:    @Test  
22:    public void TestIt() {  
24:          new ShellMenu("Help", "About Eclipse Platform").select();  
25:          new PushButton("Installation Details").click();  
27:          DefaultTree ConfigTree = new DefaultTree();  
28:          List ConfigItems = ConfigTree.getAllItems();  
30:          assertFalse ("The list is empty!", ConfigItems.isEmpty());  
31:          for (TreeItem item : ConfigItems) {  
32:               System.out.println ("Found: " + item.getText());  
33:          }  
34:    }  
35:  }  

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:

  • The Red Deer Recorder - To enable you to capture manual actions and convert them into test programs
  • How you can Extend Red Deer - To provide test coverage for your plugins’ specific functions.
  • And How you can Contribute these extensions to the Red Deer project.
  • How you can Define Complex Requirements - To enable you to perform setup tasks for your tests.
  • Red Deer’s Integration with Selenium - To enable you to test web interfaces provided by your plugins.
  • Running Red Deer tests with Jenkins - To enable you to take advantage of Jenkins’ Continuous Integration (CI) test framework.

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.

No comments: