Exercise 1: First Simple Application

Part B: Automate build and testing with Ant (OPTIONAL!)

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/
Note:
Like partA, this section is optional since we will be using Maven to create portable and repeatable builds. Notice -- however -- that everything accomplished with Ant requires explicit configuration.

Objectives

  • Demonstrate the basics of automating the manual steps in part A using the Apache Ant build tool.

Steps

  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.
    #ex1 build.properties
    M2_REPO=c:/jhu/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: build.xml
    
    echo:
         [echo] basedir=C:\cygwin\home\jcstaff\proj\784\exercises\ex1
         [echo] artifactId=ex1
         [echo] src.dir=C:\cygwin\home\jcstaff\proj\784\exercises\ex1/src
         [echo] build.dir=C:\cygwin\home\jcstaff\proj\784\exercises\ex1/target
         [echo] junit.classpath=c:/jhu/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 docs for more infomation.
    • javac - compiles java sources files. See Ant docs for more information. Note that we are making sure we get J2SE 1.6 classes compiled.
    • jar - builds a java archive. See Ant 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.6"
                   target="1.6">
                   <classpath>
                   </classpath>
            </javac>
    
            <jar destfile="${build.dir}/${artifactId}.jar">
                <fileset dir="${build.dir}/classes"/>
            </jar>
        </target>
    $ rm -rf target/; ant package
    Buildfile: build.xml
    
    package:
        [mkdir] Created dir: C:\cygwin\home\jcstaff\proj\784\exercises\ex1\target\classes
        [javac] Compiling 1 source file to C:\cygwin\home\jcstaff\proj\784\exercises\ex1\target\classes
          [jar] Building jar: C:\cygwin\home\jcstaff\proj\784\exercises\ex1\target\ex1.jar
    
    BUILD SUCCESSFUL
    Total time: 1 second
    
    > 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
    
  5. 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.6"
                   target="1.6">
                   <classpath>
                       <pathelement location="${build.dir}/${artifactId}.jar"/>
                       <pathelement path="${junit.classpath}"/>
                   </classpath>
            </javac>
        </target>
    
    $ rm -rf target/; ant
    Buildfile: build.xml
    
    package:
        [mkdir] Created dir: C:\cygwin\home\jcstaff\proj\784\exercises\ex1\target\classes
        [javac] Compiling 1 source file to C:\cygwin\home\jcstaff\proj\784\exercises\ex1\target\classes
          [jar] Building jar: C:\cygwin\home\jcstaff\proj\784\exercises\ex1\target\ex1.jar
    
    test:
        [mkdir] Created dir: C:\cygwin\home\jcstaff\proj\784\exercises\ex1\target\test-classes
        [javac] Compiling 1 source file to C:\cygwin\home\jcstaff\proj\784\exercises\ex1\target\test-classes
    
    BUILD SUCCESSFUL
    Total time: 1 second
    
    
    > 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
    
  6. 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:
    For older versions of Ant prior to 1.7.1, you may need to copy your junit.jar file into $ANT_HOME/lib to get the junit task correctly recognized by Ant. Until you do that, you may get the following error.
    Buildfile: build.xml
    
    package:
    
    test:
    
    BUILD FAILED
    C:\cygwin\home\jcstaff\proj\784\excercises\ex1\build.xml:49: Could not create ta
    sk or type of type: junit.
    
    Ant could not find the task or a class this task relies upon.
    
    This is common and has a number of causes; the usual
    solutions are to read the manual pages then download and
    install needed JAR files, or fix the build file:
     - You have misspelt 'junit'.
    
    ...
    
    Remember that for JAR files to be visible to Ant tasks implemented
    in ANT_HOME/lib, the files must be in the same directory or on the
    classpath
    $ rm -rf target/; ant
    Buildfile: build.xml
    
    package:
        [mkdir] Created dir: C:\cygwin\home\jcstaff\proj\784\exercises\ex1\target\classes
        [javac] Compiling 1 source file to C:\cygwin\home\jcstaff\proj\784\exercises\ex1\target\classes
          [jar] Building jar: C:\cygwin\home\jcstaff\proj\784\exercises\ex1\target\ex1.jar
    
    test:
        [mkdir] Created dir: C:\cygwin\home\jcstaff\proj\784\exercises\ex1\target\test-classes
        [javac] Compiling 1 source file to C:\cygwin\home\jcstaff\proj\784\exercises\ex1\target\test-classes
        [mkdir] Created dir: C:\cygwin\home\jcstaff\proj\784\exercises\ex1\target\test-reports
        [junit] Running myorg.mypackage.ex1.AppTest
        [junit] Tests run: 2, Failures: 1, Errors: 0, Time elapsed: 0.109 sec
        [junit] Test myorg.mypackage.ex1.AppTest FAILED
    
    BUILD SUCCESSFUL
    Total time: 2 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
    
  7. Test output of each test is in the TXT and XML reports.
    $ cat target/test-reports/TEST-myorg.mypackage.ex1.AppTest.txt
    Testsuite: myorg.mypackage.ex1.AppTest
    Tests run: 2, Failures: 1, Errors: 0, Time elapsed: 0.109 sec
     ------------- Standard Output ---------------
    testApp
    Here's One!
    testFail
    Here's One!
     ------------- ---------------- ---------------
    
    Testcase: testApp took 0.015 sec
    Testcase: testFail took 0 sec
            FAILED
    app didn't return 0
    junit.framework.AssertionFailedError: app didn't return 0
            at myorg.mypackage.ex1.AppTest.testFail(AppTest.java:29)
    
  8. 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>
    $ ant clean
    Buildfile: build.xml
    
    clean:
       [delete] Deleting directory C:\cygwin\home\jcstaff\proj\784\exercises\ex1\target
    
    BUILD SUCCESSFUL
    Total time: 0 seconds
    
    > find . -type f
    ./src/main/java/myorg/mypackage/ex1/App.java
    ./src/test/java/myorg/mypackage/ex1/AppTest.java
    ./build.properties
    ./build.xml
  9. Comment out the bogus testFail and rerun.
    $ ant clean test
    Buildfile: build.xml
    
    clean:
    
    package:
        [mkdir] Created dir: C:\cygwin\home\jcstaff\proj\784\exercises\ex1\target\classes
        [javac] Compiling 1 source file to C:\cygwin\home\jcstaff\proj\784\exercises\ex1\target\classes
          [jar] Building jar: C:\cygwin\home\jcstaff\proj\784\exercises\ex1\target\ex1.jar
    
    test:
        [mkdir] Created dir: C:\cygwin\home\jcstaff\proj\784\exercises\ex1\target\test-classes
        [javac] Compiling 1 source file to C:\cygwin\home\jcstaff\proj\784\exercises\ex1\target\test-classes
        [mkdir] Created dir: C:\cygwin\home\jcstaff\proj\784\exercises\ex1\target\test-reports
        [junit] Running myorg.mypackage.ex1.AppTest
        [junit] Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 0.063 sec
    
    BUILD SUCCESSFUL
    Total time: 1 second
    
    jcstaff@ejavaXP ~/proj/784/exercises/ex1
    $ cat target/test-reports/TEST-myorg.mypackage.ex1.AppTest.txt
    Testsuite: myorg.mypackage.ex1.AppTest
    Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 0.063 sec
     ------------- Standard Output ---------------
    testApp
    Here's One!
     ------------- ---------------- ---------------
    
    Testcase: testApp took 0.016 sec

Summary

In this part, we introduced Ant as a build tool to automate the tasks of building and testing our project. As you can see by looking at the list of Ant tasks, optional tasks, and externally provided tasks, there is quite a bit of support for this approach.

Ant is an excellent work-horse for doing specific tasks. Tasks are written in Java and configured with XML. The main issue against Ant is that the tasks are mostly written at the leaf level, with no formal way to supply higher level functionality. Ant neither requires or promotes any structure or rules for a project. Lacking such a framework requires that the end user develop their own structure and rules and configure each task individually. This configuration can get quite large as the project matures. The developer knowledge of how to work within that configuration is not generally portable to other projects.

Although command lines for Java SE clients (i.e., main programs not driven by JUnit) will be wrapped by Ant within class examples and projects submitted, you won't have to worry about Ant for a build environment in class. We will exclusively use Maven to manage the builds (covered later). The Ant build capability is covered here to contrast Maven in its support for Enterprise Java applications.