Enterprise Java Development@TOPIC@

Chapter 10. Automate Build and Testing with Ant (OPTIONAL!)

10.1. Summary

This chapter demonstrates the basics of automating the manual steps in the previous chapter using the Apache Ant build tool. If you just skim thru this step, please be sure to take note of how everything gets explicitly defined in Ant. There are not many rules of the road and standard defaults to live by. That will be a big contrast when working with Maven.

Note

All course examples and projects submitted will use Maven. Ant will be used to wrap command lines for Java SE clients executed outside the normal build environment. However, this exercise shows Ant only being used as part of the artifact build and test environment as a stepping stone to understanding some of the basic build and test concepts within Maven.

Note

If you do not have Ant installed on your system, it can be from http://ant.apache.org/

Warning

This chapter is optional!!! It contains many tedious steps to setup a module build using the Ant build tool -- which will not be part of class. It is presented here as an example option to building the module with shell scripts. If you wish to just skim the steps -- please do. Please do not waste time trying to get Ant to build your Java modules for this class.

  1. Create a build.properties file in $PROJECT_BASEDIR. This will be used to define any non-portable property values. Place the most non-portable base variables (.e.g, M2_REPO location) towards the top and build lower-level paths from them. This makes the scripts much easier to port to another environment. If you still have your maven repository in your $HOME directory, you can make use of ${user.home} environment variable rather than a hard-coded path.

    #ex1 build.properties
    #M2_REPO=c:/jhu/repository
    M2_REPO=${user.home}/.m2/repository
    
    junit.classpath=${M2_REPO}/junit/junit/4.10/junit-4.10.jar
  2. Create a build.xml file in $PROJECT_BASEDIR. Note the following key elements.

    • project - a required root for build.xml files

      • name - not significant, but helpful

      • default - the target to run if none is supplied on command line

      • basedir - specifies current directory for all tasks

    • property - defines an immutable name/value

      • file - imports declarations from a file; in this case build.properties created earlier

      • name/value - specifies a property within the script

    • target - defines an entry point into the build.xml script. It hosts one or more tasks.

      • name - defines name of target, which can be supplied on command line.

    • echo - a useful Ant task to printout status and debug information. See Ant docs for more information.

    
    <?xml version="1.0" encoding="utf-8" ?> 
    <!-- ex1 build.xml 
    -->
    <project name="ex1" default="" basedir=".">
        <property file="build.properties"/>

        <property name="artifactId" value="ex1"/>
        <property name="src.dir"    value="${basedir}/src"/>
        <property name="build.dir"  value="${basedir}/target"/>

        <target name="echo">
            <echo>basedir=${basedir}</echo>
            <echo>artifactId=${artifactId}</echo>
            <echo>src.dir=${src.dir}</echo>
            <echo>build.dir=${build.dir}</echo>
            <echo>junit.classpath=${junit.classpath}</echo>
        </target>
    </project>
  3. Sanity check your build.xml and build.properties file with the echo target.

    $ ant echo
    Buildfile: /home/jim/proj/784/exercises/ex1/build.xml
    
    echo:
         [echo] basedir=/home/jim/proj/784/exercises/ex1
         [echo] artifactId=ex1
         [echo] src.dir=/home/jim/proj/784/exercises/ex1/src
         [echo] build.dir=/home/jim/proj/784/exercises/ex1/target
         [echo] junit.classpath=/home/jim/.m2/repository/junit/junit/4.10/junit-4.10.jar
    
    BUILD SUCCESSFUL
    Total time: 0 seconds
  4. Add the "package" target to compile and archive your /src/main classes. Note the following tasks in this target.

    • mkdir - creates a directory. See Ant Mkdir docs for more infomation.

    • javac - compiles java sources files. See Ant Javac docs for more information. Note that we are making sure we get JavaSE 8 classes compiled.

    • jar - builds a java archive. See Ant Jar Docs for more information.

    
        <target name="package">
            <mkdir dir="${build.dir}/classes"/>
            <javac srcdir="${src.dir}/main/java"
                   destdir="${build.dir}/classes"
                   debug="true"
                   source="1.8"
                   target="1.8"
                   includeantruntime="false">
                   <classpath>
                   </classpath>
            </javac>

            <jar destfile="${build.dir}/${artifactId}.jar">
                <fileset dir="${build.dir}/classes"/>
            </jar>
        </target>
  5. Execute the "package" target just added. This should compile the production class from src/main into target/classes and build a Java archive with the production class in target/.

    $ rm -rf target/; ant package
    Buildfile: /home/jim/proj/784/exercises/ex1/build.xml
    
    package:
        [mkdir] Created dir: /home/jim/proj/784/exercises/ex1/target/classes
        [javac] Compiling 1 source file to /home/jim/proj/784/exercises/ex1/target/classes
          [jar] Building jar: /home/jim/proj/784/exercises/ex1/target/ex1.jar
    
    BUILD SUCCESSFUL
    Total time: 2 seconds

    Note

    You may get the following error when you execute the javac task. If so, export JAVA_HOME=(path to JDK_HOME) on your system to provide Ant a reference to a JDK instance.

    build.xml:26: Unable to find a javac compiler;
    com.sun.tools.javac.Main is not on the classpath.
    Perhaps JAVA_HOME does not point to the JDK.
    It is currently set to ".../jre"
    $ find . -type f
    ./src/main/java/myorg/mypackage/ex1/App.java
    ./src/test/java/myorg/mypackage/ex1/AppTest.java
    ./build.properties
    ./build.xml
    ./target/classes/myorg/mypackage/ex1/App.class
    ./target/ex1.jar
  6. Add the "test" target to compile your /src/test classes. Make this the default target for your build.xml file. Note too that it should depend on the successful completion of the "package" target and include the produced archive in its classpath.

    
    <project name="ex1" default="test" basedir=".">
    ...
        <target name="test" depends="package">
            <mkdir dir="${build.dir}/test-classes"/>
            <javac srcdir="${src.dir}/test/java"
                   destdir="${build.dir}/test-classes"
                   debug="true"
                   source="1.8"
                   target="1.8"
                   includeantruntime="false">
                   <classpath>
                       <pathelement location="${build.dir}/${artifactId}.jar"/>
                       <pathelement path="${junit.classpath}"/>
                   </classpath>
            </javac>
        </target>
  7. Execute the new "test" target after clearing out the contents of the target directory. Note that the target directory gets automatically re-populated with the results of the "compile" target and augmented with the test class from src/test compiled into target/test-classes.

    $ rm -rf target/; ant
    Buildfile: /home/jim/proj/784/exercises/ex1/build.xml
    
    package:
        [mkdir] Created dir: /home/jim/proj/784/exercises/ex1/target/classes
        [javac] Compiling 1 source file to /home/jim/proj/784/exercises/ex1/target/classes
          [jar] Building jar: /home/jim/proj/784/exercises/ex1/target/ex1.jar
    
    test:
        [mkdir] Created dir: /home/jim/proj/784/exercises/ex1/target/test-classes
        [javac] Compiling 1 source file to /home/jim/proj/784/exercises/ex1/target/test-classes
    
    BUILD SUCCESSFUL
    Total time: 3 seconds
    > find . -type f
    ./src/main/java/myorg/mypackage/ex1/App.java
    ./src/test/java/myorg/mypackage/ex1/AppTest.java
    ./build.properties
    ./build.xml
    ./target/classes/myorg/mypackage/ex1/App.class
    ./target/ex1.jar
    ./target/test-classes/myorg/mypackage/ex1/AppTest.class
    
  8. Add the junit task to the test target. The junit task is being configured to run in batch mode and write a TXT and XML reports to the target/test-reports directory. See Ant docs for more details on the junit task. Make special note of the following:

    • printsummary - produce a short summary to standard out showing the number of tests run and a count of errors, etc.

    • fork - since Ant runs in a JVM, any time you run a task that requires a custom classpath, it is usually required that it be forked into a separate process (with its own classpath).

    • batchtest - run all tests found and write results of each test into the test-reports directory.

    • formatter - write a text and XML report of results

    
            <mkdir dir="${build.dir}/test-reports"/>
            <junit printsummary="true" fork="true">
                   <classpath>
                       <pathelement path="${junit.classpath}"/>
                       <pathelement location="${build.dir}/${artifactId}.jar"/>
                       <pathelement location="${build.dir}/test-classes"/>
                   </classpath>

                <batchtest fork="true" todir="${build.dir}/test-reports">
                    <fileset dir="${build.dir}/test-classes">
                        <include name="**/*Test*.class"/>
                    </fileset>
                </batchtest>

                <formatter type="plain"/>
                <formatter type="xml"/>
            </junit>

    Note

    A few years ago when I sanity checked this exercise I got the common error below. I corrected the issue by downloading a full installation from the Ant website and exporting my ANT_HOME to the root of that installation. (export ANT_HOME=/opt/apache-ant-1.9.4) and adding $ANT_HOME/bin to the PATH (export PATH=$ANT_HOME/bin:$PATH) ANT_HOME is required for Ant to locate the junit task.

    BUILD FAILED
    
    /home/jim/proj/784/exercises/ex1/build.xml:57: Problem: failed to create task or type junit
    Cause: the class org.apache.tools.ant.taskdefs.optional.junit.JUnitTask was not found.        
            This looks like one of Ant's optional components.
    Action: Check that the appropriate optional JAR exists in
            -/usr/share/ant/lib
            -/home/jim/.ant/lib
            -a directory added on the command line with the -lib argument
    
    Do not panic, this is a common problem.
    The commonest cause is a missing JAR.
    
    This is not a bug; it is a configuration problem
  9. Execute the updated "test" target with the JUnit test.

    $ rm -rf target; ant
    package:
        [mkdir] Created dir: /home/jim/proj/784/exercises/ex1/target/classes
        [javac] Compiling 1 source file to /home/jim/proj/784/exercises/ex1/target/classes
          [jar] Building jar: /home/jim/proj/784/exercises/ex1/target/ex1.jar
    
    test:
        [mkdir] Created dir: /home/jim/proj/784/exercises/ex1/target/test-classes
        [javac] Compiling 1 source file to /home/jim/proj/784/exercises/ex1/target/test-classes
        [mkdir] Created dir: /home/jim/proj/784/exercises/ex1/target/test-reports
        [junit] Running myorg.mypackage.ex1.AppTest
        [junit] Tests run: 2, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 15.143 sec
        [junit] Test myorg.mypackage.ex1.AppTest FAILED
    
    BUILD SUCCESSFUL
    Total time: 17 seconds
    $ find . -type f
    ./src/main/java/myorg/mypackage/ex1/App.java
    ./src/test/java/myorg/mypackage/ex1/AppTest.java
    ./build.properties
    ./build.xml
    ./target/classes/myorg/mypackage/ex1/App.class
    ./target/ex1.jar
    ./target/test-classes/myorg/mypackage/ex1/AppTest.class
    ./target/test-reports/TEST-myorg.mypackage.ex1.AppTest.txt
    ./target/test-reports/TEST-myorg.mypackage.ex1.AppTest.xml

    Note

    Note the 17 seconds it took to run/complete the test seems excessive. I was able to speed that up to 0.001 sec by commenting out the XML report option (which we will not use in this exercise).

  10. Test output of each test is in the TXT and XML reports.

    $ more target/test-reports/TEST-myorg.mypackage.ex1.AppTest.txt 
    Testsuite: myorg.mypackage.ex1.AppTest
    Tests run: 2, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 15.246 sec
    ------------- Standard Output ---------------
    testApp
    Here's One!
    testFail
    Here's One!
    ------------- ---------------- ---------------
    
    Testcase: testApp took 0.007 sec
    Testcase: testFail took 0.022 sec
            FAILED
    app didn't return 0
    junit.framework.AssertionFailedError: app didn't return 0
            at myorg.mypackage.ex1.AppTest.testFail(AppTest.java:26)
  11. Add a clean target to the build.xml file to delete built artifacts. See Ant docs for details on the delete task.

    
        <target name="clean">
            <delete dir="${build.dir}"/>
        </target>
  12. Re-run and use the new "clean" target you just added.

    $ ant clean test
    Buildfile: /home/jim/proj/784/exercises/ex1/build.xml
    
    clean:
       [delete] Deleting directory /home/jim/proj/784/exercises/ex1/target
    
    package:
        [mkdir] Created dir: /home/jim/proj/784/exercises/ex1/target/classes
        [javac] Compiling 1 source file to /home/jim/proj/784/exercises/ex1/target/classes
          [jar] Building jar: /home/jim/proj/784/exercises/ex1/target/ex1.jar
    
    test:
        [mkdir] Created dir: /home/jim/proj/784/exercises/ex1/target/test-classes
        [javac] Compiling 1 source file to /home/jim/proj/784/exercises/ex1/target/test-classes
        [mkdir] Created dir: /home/jim/proj/784/exercises/ex1/target/test-reports
        [junit] Running myorg.mypackage.ex1.AppTest
        [junit] Tests run: 2, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 15.123 sec
        [junit] Test myorg.mypackage.ex1.AppTest FAILED
    
    BUILD SUCCESSFUL
    Total time: 17 seconds
  13. Comment out the bogus testFail and rerun.

    $ cat src/test/java/myorg/mypackage/ex1/AppTest.java
    ...
        //@Test
        public void testFail() {
    $ ant clean test
    Buildfile: /home/jim/proj/784/exercises/ex1/build.xml
    
    clean:
       [delete] Deleting directory /home/jim/proj/784/exercises/ex1/target
    
    package:
        [mkdir] Created dir: /home/jim/proj/784/exercises/ex1/target/classes
        [javac] Compiling 1 source file to /home/jim/proj/784/exercises/ex1/target/classes
          [jar] Building jar: /home/jim/proj/784/exercises/ex1/target/ex1.jar
    
    test:
        [mkdir] Created dir: /home/jim/proj/784/exercises/ex1/target/test-classes
        [javac] Compiling 1 source file to /home/jim/proj/784/exercises/ex1/target/test-classes
        [mkdir] Created dir: /home/jim/proj/784/exercises/ex1/target/test-reports
        [junit] Running myorg.mypackage.ex1.AppTest
        [junit] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 15.161 sec
    
    BUILD SUCCESSFUL
    Total time: 17 seconds

In this chapter you performed many of the same actions you did in the previous chapter except that you implemented in a portable build tool called Ant. Ant is very powerful and open-ended. You were shown these same steps in Ant as an intermediate towards Maven. Ant, being open-ended, does not follow many conventions. Everything must be explicitely specified. You will find that to be in significant contrast with Maven. Ant is a "build tool" and Maven is a "build system" with conventions/rules.