Hands-on Tutorial: Hello World with Ant 4

(On Windows system)

 

 

 

 

 

 

Using external libraries

 

Somehow told us not to use syso-statements. For log-Statements we should use a Logging-API - customizable on a high degree (including switching off during usual life (= not development) execution). We use Log4J for that, because:

 

  1. It is not part of the JDK (1.4+) and we want to show how to use external libs

  2. It can run under JDK 1.2 (as Ant)

  3. It's highly configurable

  4. It’s from Apache :-)

 

We store our external libraries in a new directory lib. Log4J can be downloaded [1] from Logging's Homepage. Create the lib directory and extract the log4j-1.2.9.jar into that lib-directory. After that we have to modify our java source to use that library and our buildfile so that this library could be accessed during compilation and run.

Working with Log4J is documented inside its manual. Here we use the MyApp-example from the Short Manual [2]. First we have to modify the java source to use the logging framework:

 

package oata;

 

import org.apache.log4j.Logger;

import org.apache.log4j.BasicConfigurator;

 

public class HelloWorld {

    static Logger logger = Logger.getLogger(HelloWorld.class);

 

    public static void main(String[] args) {

        BasicConfigurator.configure();

        logger.info("Hello World");          // the old SysO-statement

    }

}

 

Most of the modifications are "framework overhead" which has to be done once. The blue line is our "old System-out" statement.

Don't try to run ant - you will only get lot of compiler errors. Log4J is not inside the classpath so we have to do a little work here. But do not change the CLASSPATH environment variable! This is only for this project and maybe you would break other environments (this is one of the most famous mistakes when working with Ant). We introduce Log4J (or to be more precise: all libraries (jar-files) which are somewhere under .\lib) into our buildfile:

 

<project name="HelloWorld" basedir="." default="main">

    ...

    <property name="lib.dir"     value="lib"/>

 

    <path id="classpath">

        <fileset dir="${lib.dir}" includes="**/*.jar"/>

    </path>

 

    ...

 

    <target name="compile">

        <mkdir dir="${classes.dir}"/>

        <javac srcdir="${src.dir}" destdir="${classes.dir}" classpathref="classpath"/>

    </target>

 

    <target name="run" depends="jar">

        <java fork="true" classname="${main-class}">

            <classpath>

                <path refid="classpath"/>

                <path location="${jar.dir}/${ant.project.name}.jar"/>

            </classpath>

        </java>

    </target>

 

    ...

 

</project>

 

In this example we start our application not via its Main-Class manifest-attribute, because we could not provide a jarname and a classpath. So add our class in the red line to the already defined path and start as usual. Running ant would give (after the usual compile stuff):

 

[java] 0 [main] INFO oata.HelloWorld - Hello World

 

What's that?

 

  1. [java] Ant task running at the moment

  2. 0 sorry don't know - some Log4J stuff

  3. [main] the running thread from our application

  4. INFO log level of that statement

  5. oata.HelloWorld source of that statement

  6. - separator

  7. Hello World the message

 

For another layout ... have a look inside Log4J's documentation about using other PatternLayout's.

 

Configuration files

 

Why we have used Log4J? "It's highly configurable"? No - all is hard coded! But that is not the debt of Log4J - it's ours. We had coded BasicConfigurator.configure(); which implies a simple, but hard coded configuration. More comfortable would be using a property file. In the java source delete the BasicConfiguration-line from the main() method (and the related import-statement). Log4J will search then for a configuration as described in its manual. Then create a new file src/log4j.properties. That's the default name for Log4J's configuration and using that name would make life easier - not only the framework knows what is inside, you too!

 

log4j.rootLogger=DEBUG, stdout

 

log4j.appender.stdout=org.apache.log4j.ConsoleAppender

 

log4j.appender.stdout.layout=org.apache.log4j.PatternLayout

log4j.appender.stdout.layout.ConversionPattern=%m%n

 

This configuration creates an output channel ("Appender") to console named as stdout which prints the message (%m) followed by a line feed (%n) - same as the earlier System.out.println() :-) Oooh kay - but we haven't finished yet. We should deliver the configuration file, too. So we change the buildfile:

 

    ...

    <target name="compile">

        <mkdir dir="${classes.dir}"/>

        <javac srcdir="${src.dir}" destdir="${classes.dir}" classpathref="classpath"/>

        <copy todir="${classes.dir}">

            <fileset dir="${src.dir}" excludes="**/*.java"/>

        </copy>

    </target>

    ...

 

This copies all resources (as long as they haven't the suffix ".java") to the build directory, so we could start the application from that directory and these files will be included into the jar.

 

Testing the class

 

In this step we will introduce the usage of the JUnit [3] testframework in combination with Ant. Because Ant has a built-in JUnit 3.8.2 you could start directly using it. Write a test class in src\HelloWorldTest.java:

 

public class HelloWorldTest extends junit.framework.TestCase {

 

    public void testNothing() {

    }

   

    public void testWillAlwaysFail() {

        fail("An error message");

    }

   

}

 

Because we don’t have real business logic to test, this test class is very small: just show how to start. For further information see the JUnit documentation [3] and the manual of junit task. Now we add a junit instruction to our buildfile:

 

    ...

 

    <target name="run" depends="jar">

        <java fork="true" classname="${main-class}">

            <classpath>

                <path refid="classpath"/>

                <path id="application" location="${jar.dir}/${ant.project.name}.jar"/>

            </classpath>

        </java>

    </target>

   

    <target name="junit" depends="jar">

        <junit printsummary="yes">

            <classpath>

                <path refid="classpath"/>

                <path refid="application"/>

            </classpath>

           

            <batchtest fork="yes">

                <fileset dir="${src.dir}" includes="*Test.java"/>

            </batchtest>

        </junit>

    </target>

 

    ...

 

We reuse the path to our own jar file as defined in run-target by giving it an ID. The printsummary=yes lets us see more detailed information than just a "FAILED" or "PASSED" message. How much tests failed? Some errors? Printsummary lets us know. The classpath is set up to find our classes. To run tests the batchtest here is used, so you could easily add more test classes in the future just by naming them *Test.java. This is a common naming scheme. After a ant junit you'll get:

 

...

junit:

    [junit] Running HelloWorldTest

    [junit] Tests run: 2, Failures: 1, Errors: 0, Time elapsed: 0,01 sec

    [junit] Test HelloWorldTest FAILED

 

BUILD SUCCESSFUL

...

 

We can also produce a report. Something that you (and other) could read after closing the shell.... There are two steps: 1. let <junit> log the information and 2. Convert these to something readable (browsable).

 

    ...

    <property name="report.dir” value="${build.dir}/junitreport"/>

    ...

    <target name="junit" depends="jar">

        <mkdir dir="${report.dir}"/>

        <junit printsummary="yes">

            <classpath>

                <path refid="classpath"/>

                <path refid="application"/>

            </classpath>

           

            <formatter type="xml"/>

           

            <batchtest fork="yes" todir="${report.dir}">

                <fileset dir="${src.dir}" includes="*Test.java"/>

            </batchtest>

        </junit>

    </target>

   

    <target name="junitreport">

        <junitreport todir="${report.dir}">

            <fileset dir="${report.dir}" includes="TEST-*.xml"/>

            <report todir="${report.dir}"/>

        </junitreport>

    </target>

 

Because we would produce a lot of files and these files would be written to the current directory by default, we define a report directory, create it before running the junit and redirect the logging to it. The log format is XML so junitreport could parse it. In a second target junitreport should create a browsable HTML-report for all generated xml-log files in the report directory. Now you can open the ${report.dir}\index.html and see the result (looks something like JavaDoc). Personally I use two different targets for junit and junitreport. Generating the HTML report needs some time and you don’t need the HTML report just for testing, e.g. if you are fixing an error or an integration server is doing a job.

 

 

Resources

 

    [1] http://www.apache.org/dist/logging/log4j/1.2.13/logging-log4j-1.2.13.zip

    [2] http://logging.apache.org/log4j/docs/manual.html

    [3] http://www.junit.org/index.htm

 


 

 Ant 3 | Back to Main