AspectJ-based framework for SWT
From AbbotWiki
The following is adapted from a wiki page written by Kyle Shank.
| Table of contents |
Overview
- New framework for writing test cases
- Tests are consistent
- Developer no longer has to declare threads
- Developer doesn't have to time the test case with delays
- Helpers are now defined to encapsulate reusable test case code
- No setup or configuring of abbot within test case required
- Context defines testing environment
- This eliminates the need to re-instantiate or configure Abbot variables
- Lines of code required to produce test case drastically reduced
- In some cases from ~1600 to ~200 lines of code
Framework
The intention of this testing framework is to provide an accessible and maintainable abstraction to Abbot. The old way of defining test cases required inexact timing assumptions, lots of setup and pratically unmaintainable code that resided in dozens of inline Threads. It was far too complex and time consuming to be productive. The new approach deals with these issues by defining a standard for reusing code via helper classes, eliminating the need to fret over delays and abstracting thread operations to the framework.
SimpleCase
SimpleCase is the main abstract class from which all others derive from. It extends JUnit's TestCase and takes care of initializing the context that will be used throughout the test. SimpleCase and its inner classes do the heavy lifting when it comes to threading.
The perform method of SimpleCase is abstract and must be implemented by the helper subclass. SimpleCase.perform will invoke the specified method on the test case through reflection. It has been made abstract so the implementing helper actually performs the invocation action as opposed to the superclass that may or may not encounter IllegalAccessExceptions.
SimpleCaseWorker is an inner class that extends Thread. It is the object returned from the start(function) method. The start method will construct the SimpleCaseWorker object and thread the call to the specified function. The function is passed to the start method as a string. SimpleCase takes care of setting things up for the reflection invocation and then calls the perform method on the helper with the appropriate arguments. The reason for this abstraction to simply thread the method call is to avoid defining inline or many Thread classes that keep functionality within their defined run method. This allows the test writer to simply add the method to their class and tell the framework to thread the call. The exec(function) method works much in the same way start does except it does a safeJoin and waits for the thread to complete before returning. You can think of start as asynchronous and exec as synchronous calls to a method.
SimpleCase houses the important static utility method "waitForJobs" which joins on jobs remaining in the eclipse ob manager to ensure safe test execution. SimpleCase.waitForJobs will be hooked before every call to a helper method to validate that it is indeed okay to proceed.
While threading was at the heart of the original test cases, this new framework only uses it in a few choice situations. The safety hooks that query eclipse's job manager now protect the test case from executing at the wrong time. As a result, the test writer no longer needs to define inconsistent delays or a seperate thread for every bit of functionality. With that said the use of the threading using start method should now only be required when dealing with dialogs or other shells that abbot must wait on in order to gain access. For instance, you would need to do this if you wanted to automate action within a New Faces Action wizard. The process would entail calling start with the function that wait for the shell then clicking on the Faces Action palette item. Examples of this can be found in the test.helpers package. The bottom line is that
- threading on the part of the developer has been reduced to a minimum.
- everything extends from SimpleCase. This includes the test case the developer writes as well as any helper that gets created.
structure
The helper library introduces a new way to create test cases. One of the goals of this framework was to make it even easier for the developer to write test cases. With the helper library this is done by having the helper functions return instances of the helper class itself. This in effect creates a mini-grammar within java that doesn't break the programming flow. The following example focuses on the editor for FacesFromCB.jsp, drags a command button onto the faces page and changes its ID.
DiagramHelper.openSession(context)
.clickTab("FacesFromCB.jsp")
.dragPaletteItem("Command - Button",200,200)
.doubleClickLabel("Properties")
.doubleClickLabel("&Id:")
.keyAction(SWT.SHIFT | SWT.END)
.keyAction(SWT.DEL)
.keyString("hello");
aspects
The framework uses a single aspect (Safety.aj) to define a pointcut that will call SimpleCase.waitForJobs() before every method matching this expression public * test.helpers.*Helper.* (..). This means that the framework will automatically check and wait on existing jobs before every call to a helper method. This is done by defining an extension point on the org.aspectj.weavingruntime extension point named org.aspectj.weavingruntime.aspects. Define an aspect on the extension point and have it point to the defined aspect Safety.aj. That's it: the aspect will now be applied at runtime. The traditional approach to this issue would have been to apply some variant of the Proxy pattern. This would require maintainence of three interfaces instead of one. That doesn't help the TDD cause or encourage the creation and sharing of helper libraries.
Aspects are extremely powerful and useful in the context of testing. Here we are using it to ensure a safe and consistent GUI test environment but it's applications reach into other areas as well. FVT testing could greatly benefit from the use of aspects. Simply use the after directive on a pointcut for all the test methods and add a prompt so the test runner can verify whether or not the case passed or failed and log the result. Instead of hacking the ends of your test methods, define an aspect that can provide this functionality external to your test code.
Here is what the the Safety aspect looks like:
package test.helpers;
public aspect Safety {
pointcut safeStart():
call(public * test.helpers.*Helper.* (..));
before(): safeStart() {
SimpleCase.waitForJobs();
}
}
Note: Notice that this expression only applies the safety advice to classes ending in Helper located within the test.helpers package. Conform to this convention in order for the aspect to work or add an additional pointcut to apply the advice based on another expression.
![[Main Page]](/wiki/stylesheets/images/wiki.png)