Level: Intermediate Michael Nyika (mnyika@gmail.com), Senior Software Developer, Stelligent Incorporated
29 Apr 2008 So, you've written a bunch of unit tests. As a developer, you run your tests
multiple times per day, especially in a continuous integration environment. But how
badly would they break if the sources had to change? When Jester and Maven combine to
make Grester, you can quickly find out.
Written by Ivan Moore, Jester is an excellent tool that tests the unit tests that
programmers and developers write. The tool is based on the assumption that there will
be many areas in the code containing conditionals, loops, and case statements, as
well as areas in which the cyclomatic complexity of the classes as a whole would
suddenly spike or rise because multiple paths in execution are possible. Jester
focuses on spots in the code like these. But to run, it requires a well-formatted
classpath to the various resources.
Grester, which is an Apache Maven wrapper around Jester, alleviates the burden of
dealing with the annoyances of constructing a Java™ classpath from project
dependencies so you can test your execution points more easily, using Jester. Grester
also tries to promote some of the benefits of using Maven, which is at the heart of its
infrastructure. Jester would be extremely useful as an extra counter-check on code not
written in a test-driven manner. Such code could be legacy code from older applications
or even code more recently written by a group of developers who find Agile's
test-driven ways a little too rough as an initial guide to building quality into code.
In fact, you can use Grester to expose the limitations of writing code in a
nontest-driven manner. In my experience, scope creep and code that tends to miss or
bypass what should be its true business function increase the bug count and the amount
of zombie code (fast approaching the blob anti-pattern, even in small
sections of code, not necessarily as a single hard-to-manage module or set of modules).
This article doesn't go into the technicalities of interpreting Jester's outputs and
exactly how Jester works. For that information, see Resources
for an excellent article written by Elliott Rusty Harold or visit Ivan Moore's Web
site. This article is meant as a guide for acquiring and using the Maven plug-in wrapper around Jester.
Getting Grester
You can get Grester from either of two sources, both of which are listed in Resources. The infrastructure needed to run it is pretty
minimal: You need only Maven to build and use it. Grester is written in Groovy, a
dynamic language that has Java-like syntax with the advantages of languages like Python
and Ruby. At its heart, Grester is just another Maven plug-in for quickly running the
Jester tool, so the true power of Grester comes from Jester. For this article, Jester
V1.37 is used with the Grester V0.3 alpha release.
Jester without the groove: Why not?
If all project Java Archive (JAR) dependencies reside in a single location, running
Jester directly couldn't be easier than referencing the single directory inside the
Java classpath entry. However, when dependencies are spread out across a file system,
the problem of configuration for each Jester run can be complicated and annoying,
especially if the individual dependencies change location over time. With Maven, this
process is eased dramatically.
Jester works in every instance outside a Maven project build configuration. So, what
makes Grester so special? The answer lies in the manner in which Maven organizes its
dependencies. Far from this "arrangement" being inefficient, Maven tries to standardize
not only the manner in which Java (and, hence, Groovy) JARs and Web Archives (WARs) are
found but also where they reside.
 |
Note for Linux and UNIX users
Grester's file size is small, and when you've extracted it, you can safely delete the
actual compressed archive. Cygwin was used here on a Windows machine to show how
simple it is to extract it — even in a simulated Linux environment. It is not
recommended that Grester versions earlier than 0.3 be experimented with on Linux or
UNIX systems, however, because of some missing operating system features, although the
Windows alpha versions are pretty stable. In all versions, however, Jester V1.37 was used. |
|
For those unfamiliar with Maven, the concept of a repository is used. There is a
default local repository, located at $USER_HOME\.m2\repository, and a remote repository
configured either in the pom.xml or the settings.xml file, located at $MAVEN_HOME/conf.
Installing Grester
After you've tracked down the TAR-compressed resource (the .tar and tar.gz files are
for UNIX® and Linux®) or the Microsoft® Windows® ZIP file,
extract it. There are several ways to do this: Here, I use the Cygwin utility in Windows.
Figure 1. Extracting Grester with the Cygwin utility in Windows
You could also use the TAR utility, with the xzvf options
for tar.gz and the xvf options for the plain .tar file.
Figure 2 shows an example of this process.
Figure 2. Extracting the Grester tar.gz
file with the TAR utility
The final directory structure should look like Figure 3.
Figure 3. Grester extracted on Windows
Configure, build, and install Grester
At this point, you're ready to make Maven aware of external repositories from which
the relevant Grester Groovy dependencies can be acquired to compile and install
Grester as a Maven plug-in locally. You do this by adding two remote repositories to
the $MAVEN_HOME/conf/settings.xml file, as shown below.
Listing 1. Pointing Maven to remote repositories containing Groovy dependencies
<settings>
<profiles>
<profile>
<id>repositoryDefinitions</id>
<repositories>
.....
.....
<!-- You may have other repositories -->
....
....
<repository>
<id>apache-snapshotsv/id>
<name>Apache Snapshots Repository</name>
<url>http://people.apache.org/repo/m2-snapshot-repository</url>
<layout>default</layout>
<snapshots>
<enabled>true</enabled>
<updatePolicy>daily</updatePolicy>
<checksumPolicy>ignore</checksumPolicy>
</snapshots>
<releases>
<enabled>false</enabled>
</releases>
</repository>
.....
.....
</repositories>
...
</profile>
</profiles>
</settings>
|
Next comes the plug-in configuration for Maven, which specifies a repository for
Grester's Groovy plug-in dependencies. This plug-in-repository configuration is placed
within the same profile that was declared for the repositories (for example, the
name repositoryDefinitions was used as the name of the profile), as shown below.
Listing 2. Pointing Maven to remote repositories
containing Groovy plug-in dependencies
<settings>
<profiles>
<profile>
<id>repositoryDefinitions</id>
....
....
</repositories>
<pluginRepositories>
<!-- You may have other plug-in repositories -->
....
....
<pluginRepository>
<id>apache-snapshots</id>
<name>Apache Snapshots Repository</name>
<url>http://people.apache.org/repo/m2-snapshot-repository</url>
<layout>default</layout>
<snapshots>
<enabled>true</enabled>
<updatePolicy>daily</updatePolicy>
<checksumPolicy>ignore</checksumPolicy>
</snapshots>
<releases>
<enabled>false</enabled>
</releases>
</pluginRepository>
...
...
</pluginRepositories>
...
</profile>
</profiles>
</settings>
|
Now you can finally build the plug-in to completion. Grester requires Maven V2.0.5 or
later. If an earlier version is used, you will have problems compiling and using the
functionality in the Groovy-mojo-support dependency. When the $MAVEN_HOME/bin directory
is part of the system path of executable files, you execute the command mvn clean install from within the maven-grester-plugin directory
(the directory that contains Grester's pom.xml file), as shown below.
Figure 4. Building Grester from the command line
The build is usually fast (less than 20 seconds) to run. Figure 5 shows a successful installation window.
Figure 5. Installing Grester in Maven's local repository
 |
Grester's TDD approach
When writing Grester, I had to write the integration tests while keeping in mind the
operating system environment they would be written for. This came to be a bit challenging
because I'd start off with if . . . else clauses that matched
the operating system type, then made an assertion based on the type. After a while, the
realization that a stable, successful build across two platforms could be achieved if
tests were written for a platform while writing on that platform and not before. |
|
It's important to note just where Grester is installed in Maven's local repository. If
you are unfamiliar with Maven, its default local repository is
$USER_HOME/.m2/repository/. On a computer running Windows, by default, $USER_HOME would
most likely translate into Documents and Settings/$USERNAME/ (where
$USERNAME is the logged-in user). On a Linux/UNIX machine, $USER_HOME would
translate into /home/$USERNAME/. A quick look into a Windows local repository would
show that Grester is installed in C:/Documents and
Settings/$USERNAME/.m2/repository/org/apache/maven/plugins, and a directory named
maven-grester-plugin is created. This directory contains the version number
directory (the latest release is V0.3); within that directory is the actual maven-grester-plugin-x.x.jar file.
The reason for this structure lies in Grester's pom.xml file. As Figure 6 shows, the
groupId of the Grester project is org.apache.maven.plugins.
Any Maven plug-ins written in the Java or Groovy language that contain this string as
the value of the groupId contains mojos that are easier to
execute from the command line than Maven plug-ins that have some other arbitrary groupId. Because Grester uses this string, when executing individual
mojo goals from the command line, you don't need to prepend the groupId and artifactId.
Figure 6. Grester's groupId in the pom.xml configuration file
The maven-grester-plugin directory is created upon installation (the install goal creates this directory), as shown below. Other standard
Maven plug-ins are installed in the same parent directory, such as the
maven-surefire-plugin and maven-install-plugin directories.
Figure 7. Grester in Maven's local repository
The use of the special groupId string is advantageous when
the custom group and artifact IDs for the project are either lengthy and hard to
remember or just cumbersome to type repeatedly. This is why the basic Maven goals from
its default plug-ins (for example, maven-compiler-plugin or maven-surefire-plugin),
such as compile, test, or even
test-compile, do not require a command like: mvn
org.apache.maven.plugin:maven-compiler-plugin:2.0.2:compile or mvn org.apache.maven.plugin:maven-surefire-plugin:2.3:test for their
execution (only mvn compile or mvn test).
Installing Jester as Grester's main dependency
At this point, you have everything in place except the actual Jester dependency, which
is the core of Grester. There are two convenience scripts from the Windows and
Linux/UNIX platforms that would install Jester (that is, the actual jester-1.37.jar
file) into Maven's local repository. Why are they provided? Couldn't you just download
them from the same external sources as Maven when it gets its compiler, installer, and
other plug-in dependencies? The answer is that Jester isn't out there on a publicly
available and known Maven repository (such as Ibiblio for Maven), so you can't
configure Maven's $MAVEN_HOME/conf/settings.xml file with a remote repository that
contained Jester (irrespective of the way it was installed with a groupId-artifactId-version combination).
Hence, the install-jester.bat and install-jester.sh executable files have been provided
for Windows and Linux/UNIX, respectively. In case the execution of either one fails on
either platform, you can use the command shown below as a fail-safe.
Figure 8. Installing the Jester dependency
Note: After I completed this article, Grester V1.0.1 was published to the publicly available Maven
repository. This incremental improvement means that you can now obtain the plug-in
directly from a well-known Apache repository, but the Jester core JAR and instructions
for the combined use are still required to form a complete picture.
Using Grester in an example Maven project
So, you've got a nicely woven Maven project, and you'd like to test Jester on your unit
tests (or at least on a group of tests). Regardless of whether the tests are unit
or integration tests, it would be wise to either copy the project elsewhere in your
file system and run Jester on that copy or use the existing copy but be prepared to
revert any changes to your code source files. This is because Jester changes an
existing source code file, saves the change and recompiles the code (leaving the
classfile in the same directory as the source file). If the project has a relatively
small code base or the tests chosen are few, the existing code base copy can be used.
Set up the example files in Eclipse
For a test example, you'll use a basic Maven project constructed and prepared in the
Eclipse IDE. Just how you construct a Maven project and create the necessary file
within a particular development environment is beyond the scope of this article,
although the Resources section contains links and information
on how to do it. Figure 9 illustrates the project inside the Eclipse IDE.
Figure 9. Example Maven project with the Eclipse IDE
As an example, use a relatively simple class and a test class to go along with it. The
class deals with the execution of an external process command using the Java language.
Listing 3 shows the main parts of the class under test.
Listing 3. Example class under test in the Maven project
package com.prometheus.run;
import java.io.IOException;
import java.io.InputStream;
public class CommandExecutor extends Executor{
...
public String executeCommand(String command){
...
try {
Process child = performCommandExecution(command);
stream = child.getInputStream();
sb = processStream(stream);
...
}
...
return sb.toString();
}
protected StringBuffer processStream(InputStream stream) throws IOException {
...
sb = new StringBuffer();
while ((c = stream.read()) != -1) {
sb.append((char)c);
}
return sb;
}
...
}
|
Within the CommandExecutor class, the executeCommand() method makes a call on a protected method within
the same class, processStream(). Within the processStream() method, a new StringBuffer instance is created and the InputStream manipulated within a while()
loop. Listing 4 shows the test class, again showing the main sections of the test.
Listing 4. Example test class in the Maven project
package com.prometheus.run;
import com.prometheus.run.CommandExecutor;
...
public class CommandExecutorTest extends TestCase {
...
public class MockProcess extends Process{
...
public InputStream getInputStream(){
String source= "This is a mock string";
return new ByteArrayInputStream(source.getBytes());
}
public OutputStream getOutputStream(){
return null;
}
public int waitFor(){
return 1;
}
}
public void testExecuteCommmand(){
String expected = "This is a mock string";
String actual = commandExecutor.executeCommand("lsmod");
assertEquals(expected, actual);
...
}
}
|
The test class, CommandExecutorTest, is relatively simple.
Although not too much detail has been given, the basic aim of this unit test is to mock
out the behavior of the Process class through the method
call performCommandExecution() from the class under test.
It is important to note that for Grester to run successfully, the project has to
compile both code sources and test sources and run any and all tests successfully.
(Note that because of this, the test-compile Maven phase
marks the phase from which a run of Grester is allowed and not before.) The next step
is simply to attach a Maven plug-in configuration for Grester in the project's pom.xml
file. This configuration is placed in the default build section of the pom.xml
file or within any legitimate Maven profile.
Wire Grester into the project
Listing 5 shows the example configuration of the Grester plug-in placed within the
example project's pom.xml file. Notice that the groupId
corresponds to org.apache.Maven.plugins and that the version is the latest Grester
plug-in: V0.3.
Listing 5. Grester plug-in configuration in the example project
<plugins>
...
...
<!-- START MAVEN GRESTER PLUG-IN CONFIGURATION -->
<plugin>
<groupId>org.apache.Maven.plugins</groupId>
<artifactId>Maven-Grester-plugin</artifactId>
<version>0.3</version>
<configuration>
<codeSources>src/main/java/com/prometheus/run</codeSources>
<testSuiteClass>com.prometheus.run.CommandExecutorTest</testSuiteClass>
</configuration>
<executions>
<execution>
<id>inspectSourcesCodeWithGrester</id>
<phase>test</phase>
<goals>
<goal>inspect</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- END MAVEN GRESTER PLUG-IN CONFIGURATION -->
...
</plugins>
|
Notice that the project has been set to run Grester's inspect goal during Maven's test phase. The codeSources point to a directory that contains the sources for which
the test class, CommandExecutorTest, exists. It could just
as easily have pointed to the actual class, CommandExecutor,
excluding the file name extension. In the README.txt file that comes with Grester, the
extension .Groovy is mentioned, but it should be noted that there is no current support
for using Grester against Groovy sources.
As of V0.3 alpha, Grester as a plug-in has two main goals (all lowercase when used),
executable at any valid Maven life-cycle phase:
-
inspect
— This is Grester's main goal,
usually executed in the test phase (although strictly speaking, it can be any phase
after the test-compile phase). Grester creates a viable Java classpath from the
dependencies listed in the pom.xml file and feeds Jester the new classpath in turn.
-
help
— This goal, used mainly for
references on the correct plug-in syntax and structure, can be executed in isolation on
the command line as mvn grester:help.
Run Grester on the example project
Running the simple mvn clean install command (or any
life-cycle command that contains the specific phase that the inspect goal uses), would produce the output shown below.
Figure 10. Jester at work on the example code
Upon closer examination, you can see that line 27 in the original class file CommandExecutor has been changed from -1
to 1. It may take a while for Jester to perform a complete
operation on a single class. At the end of the operation, a jesterReport.xml file is
produced that shows summary details of what happened in the Java Swing window.
Asking for Grester help
Running mvn grester:help from the command line
produces output similar to Figure 11. It serves as a simple short guide to configuring
Grester without referring to the original README.txt file.
Figure 11. Grester's help goal
Conclusion
Grester is not a perfect plug-in and is still being improved. Direct support for
Groovy sources would be particularly helpful. The same idea be could apply to projects
that don't use Maven yet require the construction of a Java classpath string across,
for example, Apache Ant build files that list dependencies across multiple directories
in a single file system. If the Ant files themselves have been split into many separate
files, the process could be more difficult.
It is hard to say whether it's really worth going through the trouble of running a
single tool (Jester) on projects whose dependencies cannot be readily identified easily
in a single location, but it's also my impression that Jester is an important tool
for testing the robustness of the way developers write tests — indeed, their
very Test-Driven Development (TDD) and even Behaviour-Driven Development (BDD) skills
are called into question when a Jester report shows poor unit- or integration-test
performance for significant changes to a code base using a static set of tests.
Resources Learn
Get products and technologies
Discuss
About the author  | 
|  | Michael Nyika is a J2EE consultant in the northern Virginia area. He has been developing software on both the Java and Microsoft .NET platforms for seven years and is a Linux/SELinux enthusiast. |
Rate this page
|