Chapter 9. Running JUnit tests from Ant

It’s supposed to be automatic, but you still have to press the button.

John Brunner

This chapter covers

  • Introducing Ant and Ivy
  • Running Ant JUnit tasks
  • Creating reports

In this chapter, we look at Apache Ant[1] or Ant for short, a free and open source build tool with direct support for JUnit. You can use Ant with any Java programming environment. We show you how to be more productive with JUnit by running tests as part of the build. We also show you how to set up your environment to build Java projects, manage JAR file dependencies, execute JUnit tests, and generate JUnit reports.

1http://ant.apache.org/

9.1. A day in the life

For unit tests to be effective, they must be part of the development routine. Most development cycles begin by checking out code from the source code repository. Before making any changes, prudent developers first run all unit test suites. Many teams have a rule that all unit tests in the repository must pass. Before starting any development, you should check to make sure all tests pass. You should always be sure that your work starts from a known stable baseline.

The next step is to write the code for a new use case (or modify an existing one.) If you’re a test-driven development (TDD) practitioner, you’ll start by writing new tests for the use case (for more about TDD, see chapter 5). In general, the tests will show that your use case isn’t supported by not compiling or failing when executed. Once you write the code to implement (correctly) the use case, the tests pass, and you can check in your code. Non-TDD practitioners implement the use case first and then write the tests. Once the tests pass, the developer checks in the code.

Before you move on to code the next feature, you should have tests to prove the feature works. After you implement the feature, you can run the tests for the entire project, ensuring that the new feature didn’t break any existing tests. If the existing code needs to change to accommodate the new feature, you should update the tests first and then make the changes.

If you test rigorously, both to help you write new code (TDD) and to ensure existing code works with new features (regression testing[2]), you must continually run the unit tests as a normal part of the development cycle. You need to be able to run these tests automatically and effortlessly throughout the day.

2 For more about regression testing, see chapter 4.

In chapter 1, section 1.4, we discussed running JUnit from the command line. Running a single JUnit test case against a single class isn’t difficult. But it isn’t practical to run continuous tests in a project with hundreds or even thousands of classes.

A project that’s fully tested can have as many test classes as production classes. You can’t expect developers to run an entire set of regression tests every day manually. Therefore, you need a way to run tests easily and automatically.

Because you’re writing so many tests, you need to write and run tests in the most effective way possible. Ant is the de facto standard tool for building Java applications; it’s an excellent tool for managing and automating JUnit tests.

9.2. Running tests from Ant

Compiling and testing a single class, like the DefaultController class from chapter 3, isn’t difficult. Compiling a larger project with multiple classes can be a huge headache if your only tool is the javac command-line compiler. When the number of classes goes up, more classes need to be on the compiler classpath. Usually, in any one build you’ll have changed only a few classes, which leads us to the issue of minimizing how much to rebuild. Rerunning your JUnit tests by hand after each build can be equally inconvenient for the same reasons.

Ant can solve both problems. Ant is not only an essential tool for building applications but also a great way to run your JUnit tests.

9.3. Introducing and installing Ant

One reason for Ant’s popularity is that it’s more than a tool: Ant is a framework for running code. In addition to using Ant to configure and launch a Java compiler, you can use it to copy files, run JUnit test suites, and create reports.

You configure Ant through an XML document called a build file, which is named build.xml by default. The Ant build file describes each task that you want to perform in your project. A build file can have several targets, or entry points, so that you can run a single target or chain several targets together. Let’s look at using Ant to run tests automatically as part of the build. If you don’t have Ant installed, see the following sidebar. For full details, consult the Ant manual (http://ant.apache.org/manual/).

Now that you’re familiar with Ant and have it installed, let’s get started with a project.

 

Installing Ant on Windows

To install Ant on Windows:

  1. Unzip the zip distribution file to a local directory (for example, C:Ant). In this directory, Unzip creates a subdirectory for the Ant distribution, for example, C:Antapache-ant-1.8.0.
  2. Add an ANT_HOME variable to your environment with this directory as the value, for example: Variable name: ANT_HOME Variable value: C:Antapache-ant-1.8.0
  3. Edit your PATH environment variable to include the %ANT_HOME%in folder: Variable name: PATH Variable value: %ANT_HOME%in;%PATH%
  4. We recommend that you specify the location of your Java Development Kit (JDK) as the JAVA_HOME environment variable: Variable name: JAVA_HOME Variable value: C:jdk1.6.0_14 This value, like the others, may vary depending on where you installed the JDK on your system.
  5. To enable Ant’s JUnit task, copy the file junit.jar to the %ANT_HOME%lib folder. Ant will add the JAR file to the classpath for your build. We look at other options later.

 

 

Installing Ant on UNIX (Bash)

To install Ant on UNIX (or Linux), follow these steps:

  1. Untar the Ant tarball to a local directory (for example, /opt/ant). In this directory, tar creates a subdirectory for the Ant distribution, for example, /opt/ ant/apache-ant-1.7.1.
  2. Add this directory to your environment as ANT_HOME, for example:
    export ANT_HOME=/opt/ant/apache-ant-1.8.0
  3. Add the ANT_HOME/bin folder to your system’s command path:
    export PATH=${PATH}:${ANT_HOME}/bin
  4. We recommend that you specify the location of your JDK as the JAVA_HOME environment variable:
    export JAVA_HOME=/usr/java/jdk1.6.0_14/
  5. To enable Ant’s JUnit task, copy the file junit.jar to the ${ANT_HOME}/lib folder. Ant will add the JAR file to the classpath for your build. We look at other options later.

 

9.4. Ant targets, projects, properties, and tasks

When you build a project, you often want to produce more than binary code. For a distribution, you may also want to generate Javadocs. For an internal development build, you might skip Javadoc generation. At times, you may want to run a build from scratch. You may also want to compile only the classes that have changed. To help you manage the build process, a build file may have several targets, encapsulating the different tasks needed to create your application and related resources.

To make the build files easier to configure and reuse, Ant lets you define property elements (similar to a constant in programming, or a final field in Java). The core Ant concepts are as follows:

  • Build file— Each build file is usually associated with a particular development project. Ant uses the project XML tag as the root element in a build file to define the project. It also lets you specify a default target, so you can run Ant without any parameters.
  • Target— When you run Ant, you can specify one or more targets to execute. Targets can optionally declare dependencies on other targets. If you ask Ant to run one target, Ant will execute its dependent targets first. This lets you create, for example, a distribution target that depends on other targets, like clean, compile, javadoc, and war.
  • Property elements— Because many of the targets within a project share the same settings, Ant lets you create property elements to encapsulate specific settings and reuse them throughout a build file. To refer to a property in a build file, you use the notation ${property}. To refer to the property named target.dir, you’d write ${target.dir}.

As we mentioned, Ant isn’t so much a tool as a framework for running tools. You can use property elements to set the parameters a tool needs and a task to run that tool. A great number of tasks come bundled with Ant, but you can also write your own custom tasks. For more about developing with Ant, we recommend reading Ant in Action[3] and exploring the Ant website.

3http://manning.com/loughran/

Listing 9.1 shows the start of the build file for the example project from chapter 3. This segment of the build file sets the default target and the properties your targets and tasks will use.

Listing 9.1. Start of an Ant build file

The listing starts by giving the project the name example and setting the default target to test. The test target appears in listing 9.3.

Next, we include the build.properties file . Because programmers may store JAR files in different locations, it’s a good practice to use a build.properties file to define their locations. Many open source projects provide a sample build.properties file you can copy and edit to match your environment.

We use the property Ant task to define the directories for production and test source code as well as the location of the directories for compiled production and test code . It’s a good practice to define source and test directories in separate locations for both source and compiled code. In our example, we end up with four property definitions. Splitting out compiled production and test code makes it easy to build other artifacts such as JAR files because all compiled production code is rooted in its own directory.

An important aspect of Ant properties is that they’re immutable. Once you set a property value, you can’t change it. If you try to redefine the value with another property element, Ant ignores the request.

9.4.1. The javac task

For simple projects, running the javac compiler from the command line is easy enough. For products with multiple packages and source directories, the configuration of the javac compiler becomes more complicated. The javac Ant task allows a build file to invoke the compiler so you no longer need to rely on the command line. Ant provides a task for almost all build-related jobs that you’d otherwise do on the command line, including dealing with repositories like CVS. You can also find tasks not defined by Ant itself that deal with just about anything. We mentioned CVS, but if you’re using Subversion for revision control, you can use SvnAnt[4] out of the Subclipse project.

4http://subclipse.tigris.org/svnant.html

A standard practice is to create compile targets in your build file to invoke the Ant javac task to compile production and source code. The javac task lets you set all compiler options, such as the source and destination directories, through task attributes.

Listing 9.2 shows the production and test compile targets that call the Java compiler.

Listing 9.2. The build file compile targets

First, we declare the target compile.java to make sure that the destination directory exists and compile the production sources . Ant resolves the property target.classes.java.dir set at the top of the build file (in listing 9.1) and inserts it in place of ${target.classes.java.dir}. The mkdir task creates a directory , but if it already exists, Ant continues quietly. The build then calls the Java compiler with javac, giving it the destination and source directories.

The compile.test target has a dependency on the compile.java target, specified with the depends attribute (depends="compile.java"). When you call the compile.test target, if Ant hasn’t called the compile.java target yet, it does so immediately.

You may have noticed that you don’t explicitly add the JUnit JAR to the classpath. Remember that when you installed Ant, you copied the JUnit JAR file to the ${ANT_HOME}/lib directory in order to use the junit Ant task. Because junit.jar is already on your classpath, you don’t need to specify it in the javac task to compile your tests.

You need to add a nested classpath element in order to add the production classes you just compiled to the classpath because the test classes call the production classes.

Last, we have a compile target that depends on the compile.java and compile. test targets.

9.4.2. The JUnit task

In chapter 3, we ran the DefaultController tests manually. To test changes, we had to do the following:

  • Compile the source code.
  • Run the TestDefaultController test case against the compiled classes.

We can get Ant to perform both steps as part of the same build target. Listing 9.3 shows the test target.

Listing 9.3. The build file test target

We declare the test target and define it to depend on the compile target . If we ask Ant to run the test target, it’ll run the compile target before running the test target (unless Ant has already called compile). The only task defined in this target is to call junit . The junit printsummary attribute causes the task to print a one-line summary at the end of the test. Setting fork to yes forces Ant to use a separate Java Virtual Machine for each test. Although this is a performance hit, it’s a good practice if you’re worried about interference between test cases. The haltonfailure and haltonerror attributes direct the build to stop if any test returns an error or a failure. In Ant, an error is an unexpected error, like an exception, whereas a failure is a failed assert call. We configure the junit task formatter to use plain text and output the test result to the console . The test name attribute defines the class name of the test to run . Finally, we extend the classpath to use for this task to include the production and test classes we just compiled .

This makes up our first test target; next, we run the Ant build.

9.5. Putting Ant to the task

Now that you’ve assembled the build file, you can run it from the command line by changing to your project directory and typing ant. Figure 9.1 shows the Ant console output.

Figure 9.1. Running the build file from the command line

We can now build and test the project at the same time. If any of the tests fail, the haltonfailure and haltonerror settings will stop the build, bringing the problem to our attention.

 

Running the junit optional task

The junit task is an optional component bundled in Ant’s ant-junit.jar file, which should already be in your ${ANT_HOME}/lib directory. Ant doesn’t bundle a copy of JUnit, so you must ensure that junit.jar is on your classpath or in the ${ANT_HOME}/lib directory. The ant-junit.jar file contains the task itself. For more information on installing Ant, see section 9.3, “Introducing and installing Ant.” If you have any trouble running the Ant build files presented in this chapter, make sure the ant-junit.jar file is in the ${ANT_HOME}/lib folder and junit.jar is either on your classpath or in the ${ANT_HOME}/lib folder.

 

So far, we’ve looked at one way to execute tests with Ant. We now look at another aspect of the build process: dependency management. In order to automate management of dependencies for Ant projects, we introduce and use the Apache Ivy[5] project.

5http://ant.apache.org/ivy/

9.6. Dependency management with Ivy

When your project is small, it can be easy to deal with the JAR files your code depends on. In our previous example, we depended on only one JAR file, junit.jar. For larger projects, your build may end up depending on dozens of libraries. These dependencies can trip up new developers or downstream developers of the project. Having to know what the JAR dependencies are and where to get them on the web should not be an impediment to developers using your project.

The Apache Maven project first introduced dependency management for Java projects (see the next chapter for coverage of the Maven build tool.) Maven dependency management is based on the concept of one or more (internet, network, or local) repositories containing JARs from many projects from all over the open source world. The developer lists dependencies for a project in a configuration file and lets Maven download the right files to a local repository cache on your machine. You can then add the JARs to your classpath based on their location in the local repository.

Apache Ivy[6] is a popular open source dependency management tool (used for recording, tracking, resolving, and reporting dependencies), focusing on flexibility and simplicity. Although available as a standalone tool, Ivy works particularly well with Ant, providing a number of powerful Ant tasks ranging from dependency resolution to reporting and publication. It’s out of the scope of this book to cover Ivy in depth, but we rework our build file with dependency management using Ivy.

6http://ant.apache.org/ivy/

The installation of Ivy is straightforward; download the zip file from the website, extract it, and copy the ivy-vvv.jar (where vvv stands for the Ivy version) to the ${ANT_HOME}/lib directory.

Ivy works in the same manner as Maven and even uses Maven repositories for resolving and downloading dependencies. You specify the dependencies for your project in a file named, by default, ivy.xml. Ivy will download all the dependencies listed in the ivy.xml file into a local cache directory.

Listing 9.4 shows the build file with changes highlighted in bold.

Listing 9.4. Adding the Ivy changes to the build file

We start by declaring the ivy namespace and calling the Ivy Ant task to retrieve the dependencies listed in the ivy.xml file from the public repository . After we retrieve the junit.jar with Ivy, the archive is in the lib folder of our project. Next, we define the junit.jar property to point to the JAR file we just downloaded . We include the JAR in the classpath of the javac task and in the classpath of the junit task . We no longer need the junit.jar in the ${ANT_HOME}/lib folder, so we can delete it.

The file ivy.xml defines the project dependencies as shown in listing 9.5.

Listing 9.5. The ivy.xml file with the listed dependencies

First, the root tag ivy-module defines the version of Ivy we want to use (in this case 2.0). Then, the info tag defines the organization and the module name for which we’re defining dependencies. Finally, the dependencies nested element is where we specify our dependencies. Our module has only one dependency listed: JUnit.

Invoking Ant again gives us the same result (figure 9.2) as when junit.jar was in the ${ANT_HOME}/lib folder, but this time we see Ivy invoked and resolve the project dependencies.

Figure 9.2. Ant output with Ivy at work

In the next section, we try out another kind of report.

9.7. Creating HTML reports

The console output from figure 9.1 might be acceptable when you’re running tests interactively, but you can’t use the output if you want to examine the results later. A (cron) job or a continuous integration server (see chapter 11) might run the tests automatically every day such that the console output might not be available.

The junit task can produce XML documents detailing the results of various test runs. Another optional Ant task, junitreport, transforms this XML into HTML using an XSL stylesheet. The result is a report you can view with any web browser. Figure 9.3 shows a report page for the example project.

Figure 9.3. Ant JUnit HTML report

Listing 9.6 shows the changes necessary (in bold) to the build file to generate this report.

Listing 9.6. Adding a JUnitReport task to the build file

First, we define a property for the location where the report will be generated and then create that directory . We modify the junit task to output the test results as XML instead of plain text. The junitreport transforms the XML test results into an HTML report using XSL. We change the junit task to create an XML report file in the ${target.report.dir} directory and then create a new report target that generates the HTML report .

We begin the report target by creating the directory where the HTML report will be generated . We call the junitreport task to create the report . The task scans the XML test results specified as an Ant fileset and generates the HTML report to our specified location .

The next step is learning how to batch tests.

 

The future of JUnit reports

The Ant junit task produces XML output containing detailed results of the execution of JUnit tests. This task isn’t the only tool to produce XML documents of this format; so does Maven’s Surefire plug-in. Several tools also consume this format in addition to junitreport, like maven-surefire-reports, and different CI servers.

As of Ant 1.7.1, the generated HTML reports don’t list skipped tests because Ant doesn’t use JUnit 4 features. Note that in the previous version of JUnit there was no special status for skipped tests. We should expect the XML schema for JUnit reports to evolve, a topic currently under discussion on the Apache Ant[7] wiki.

7http://wiki.apache.org/ant/Proposals/EnhancedTestReports

 

9.8. Batching tests

Our current build file calls the junit task through the test target to invoke specific test cases. Although this is fine for a small set of test classes, it may become unwieldy for larger sets of classes. One way to remedy this situation is to group tests together in a test suite and invoke the test suite from Ant.

Alternatively, you can direct Ant to batch tests together by using wildcards to find tests by class names. The batchtest element uses a fileset with wildcards to find test classes, as shown in listing 9.7 (with changes from listing 9.4 in bold).

Listing 9.7. Using batchtest to find test cases

The tests property defines the class name pattern used by the batchtest element later in the listing. Defining this property allows us to override it from the command line or by another property definition. This allows us, for instance, to run a single test case or provide a value that runs a narrower set of tests. This technique provides us a shortcut to run the test for any given class we’re working on while leaving the default value to execute the full set of tests. The following example executes only the TestDefaultController test case:

ant –Dtests=TestDefaultController test

The batchtest element makes the test target and our build more flexible. It’s always a good practice to include a clean target to remove all build-generated files. Doing so lets us build from first principles (in our case, Java source files), removing potential side effects from obsolete classes. Typically, a dist (for distribution) target generates all project distributable files and depends on the clean target.

You should give thought to your test class names such that you can match them using a reasonable pattern. Depending on your language background, you may choose to prefix or postfix your class names with Test or TestCase, for example, DatabaseAccessorTest.

 

Are automated unit tests a panacea?

In brief, no. Although automated tests can find a significant number of bugs, manual testing is still required to find as many bugs as possible. In general, automated regression tests catch 15–30 percent of all bugs found; manual testing finds the other 70–85 percent.

Are you sure about that?

Some test-first enthusiasts are now reporting remarkably low numbers of bug counts, approximately one to two per month or fewer. Formal studies need to substantiate these reports. Your mileage will definitely vary.

 

9.9. Summary

In this chapter, we introduced Apache Ant, one of the best tools for building Java software. We looked at the basics of an Ant build file and described key tasks: javac junit and junitreport. These tasks allow you to compile Java code, run JUnit tests, and create HTML test reports. We also introduced Apache Ivy to manage with ease your project’s JAR file dependencies. Ivy resolves and downloads JAR file dependencies for your build.

In the following chapters, we continue to explore the continuous integration paradigm with Maven, another tool for building software. We start with core Maven concepts and explore two important plug-ins: maven-surefire and maven-surefire-report. We also look at how Maven handles dependency management in comparison to Ivy.

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

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