Jakarta Project 将 Ant 工具说成“不带 make 缺点的 make”。Ant 正在成为开放源代码世界中实际上的标准。原因很简单:Ant 是使用 Java 语言编写的,这种语言可以让构建过程在多种平台上使用。这种特性简化了在不同 OS 平台之间的程序员的合作,而合作是开放源代码社区的一种需要。您可以在自己选择的平台上进行开发 和构建。Ant 的特性包括:
类可扩展性 Java 类可用于扩展构建特性,而不必使用基于 shell 的命令。
开放源代码 因为 Ant 是开放源代码,因此类扩展示例很充足。我发现通过示例来学习非常棒。
XML 可配置 Ant 不仅是基于 Java 的,它还使用 XML 文件配置构建过程。假设构建实际上是分层的,那么使用 XML 描述 make 过程就是其逻辑层。另外,如果您了解 XML,要学习如何配置构建就更简单一些。
图 2 简要介绍了一个配置文件。配置文件由目标树构成。每个目标都包含了要执行的任务,其中任务就是可以执行的代码。在本例中, mkdir是目标 compile的任务。 mkdir是建立在 Ant 中的一个任务,用于创建目录。 Ant 带有一套健全的内置任务。您也可以通过扩展 Ant 任务类来添加自己的功能。
每个目标都有唯一的名称和可选的相关性。目标相关性需要在执行目标任务列表之前执行。例如图 2 所示,在执行 compile 目标中的任务之前需要先运行 JUNIT 目标。这种类型的配置可以让您在一个配置中有多个树。
图 2. Ant XML 构建图

与经典 make 实用程序的相似性是非常显著的。这是理所当然的,因为 make 就是 make。但也要记住有一些差异:通过 Java 实现的跨平台和可扩展性,通过 XML 实现的可配置,还有开放源代码。
下载和安装 Ant
首先下载 Ant(请参阅 参考资料 )。将 Ant 解压缩到 tools 目录,再将 Ant bin 目录添加到路径中。(在我的机器上是 e:\tools\ant\bin 。)设置 ANT_HOME 环境变量。在 NT 中,这意味着进入系统属性,然后以带有值的变量形式添加 ANT_HOME。ANT_HOME 应该设置为 Ant 根目录,即包含 bin 和 lib 目录的目录。(对我来说,是 e:\tools\ant 。)确保 JAVA_HOME 环境变量设置为安装了 JDK 的目录。Ant 文档有关于安装的详细信息。
下载和安装 JUnit
下载 JUnit 3.2(请参阅 参考资料 )。解开 junit.zip ,并将 junit.jar 添加到 CLASSPATH。如果将 junit.zip 解包到类路径中,可以通过运行以下命令来测试安装: java junit.textui.TestRunner junit.samples.AllTests
定义目录结构
在开始我们的构建和测试过程之前,需要一个项目布局。图 3 显示了我的样本项目的布局。下面描述了布局的目录结构:
build -- 类文件的临时构建位置。构建过程将创建这个目录。
src -- 源代码的位置。 Src 被分为 test 文件夹和 main 文件夹,前者用于所有的测试代码,而后者包含可交付的代码。将测试代码与主要代码分离提供了几点特性。首先,使主要代码中的混乱减少。其次,它允许包对齐。我就热衷与将类和与其相关的包放置在一起。测试就应该和测试在一起。它还有助于分发过程,因为你不可能打算将单元测试分发给客户。
在实际中,我们有多个目录,例如 distribution 和 documentation 。我们还会在 main 下有多个用于包的目录,例如 com.company.util 。
因为目录结构经常变动,所以在 build.xml 中有这些变动的全局字符串常数是很重要的。
图 3. 项目布局图

Ant 构建配置文件示例
下一步,我们要创建配置文件。清单 4 显示了一个 Ant 构建文件示例。构建文件中的关键就是名为 runtests 的目标。这个目标进行分支判断并运行外部程序,其中外部程序是前面已安装的 junit.textui.TestRunner 。我们指定要使用语句 test.com.company.AllJUnitTests 来运行哪个测试套件。
清单 4. 构建文件示例
2 <property name="build.dir" value="build/classes" />
3 <target name="JUNIT">
4 <available property="junit.present" classname="junit.framework.TestCase" />
5 </target>
6 <target name="compile" depends="JUNIT">
7 <mkdir dir="${build.dir}"/>
8 <javac srcdir="src/main/" destdir="${build.dir}" >
9 <include name="**/*.java"/>
10 </javac>
11 </target>
12 <target name="jar" depends="compile">
13 <mkdir dir="build/lib"/>
14 <jar jarfile="build/lib/${app.name}.jar"
15 basedir="${build.dir}" includes="com/**"/>
16 </target>
17 <target name="compiletests" depends="jar">
18 <mkdir dir="build/testcases"/>
19 <javac srcdir="src/test" destdir="build/testcases">
20 <classpath>
21 <pathelement location="build/lib/${app.name}.jar" />
22 <pathelement path="" />
23 </classpath>
24 <include name="**/*.java"/>
25 </javac>
26 </target>
27 <target name="runtests" depends="compiletests" if="junit.present">
28 <java fork="yes" classname="junit.textui.TestRunner"
29 taskname="junit" failonerror="true">
30 <arg value="test.com.company.AllJUnitTests"/>
31 <classpath>
32 <pathelement location="build/lib/${app.name}.jar" />
33 <pathelement location="build/testcases" />
34 <pathelement path="" />
35 <pathelement path="${java.class.path}" />
36 </classpath>
37 </java>
38 </target>
39 </project>
40
运行 Ant 构建示例
开发过程中的下一步是运行将创建和测试 HelloWorld 类的构建。清单 5 显示了构建的结果,其中包括了各个目标部分。最酷的那部分是 runtests 输出语句:它告诉我们整个测试套件都正确运行了。
我在图 4 和图 5 中显示了 JUnit GUI,其中所要做的就是将 runtest 目标从 junit.textui.TestRunner 改为 junit.ui.TestRunner 。当您使用 JUnit 的 GUI 部分时,您必须选择退出按钮来继续构建过程。如果使用 Junit GUI 构建包,那么它将更难与大型的构建过程相集成。另外,文本输出也与构建过程更一致,并可以定向输出到一个用于主构建记录的文本文件。这对于每天晚上都要进行的构建非常合适。
清单 5. 构建输出示例
2 Searching for build.xml ...
3 Buildfile: E:\projects\sample\build.xml
4 JUNIT:
5 compile:
6 [mkdir] Created dir: E:\projects\sample\build\classes
7 [javac] Compiling 1 source file to E:\projects\sample\build\classes
8 jar:
9 [mkdir] Created dir: E:\projects\sample\build\lib
10 [jar] Building jar: E:\projects\sample\build\lib\sample.jar
11 compiletests:
12 [mkdir] Created dir: E:\projects\sample\build\testcases
13 [javac] Compiling 3 source files to E:\projects\sample\build\testcases
14 runtests:
15 [junit] ..
16 [junit] Time: 0.031
17 [junit]
18 [junit] OK (2 tests)
19 [junit]
20 BUILD SUCCESSFUL
21 Total time: 1 second
图 4. JUnit GUI 测试成功

图 5. JUnit GUI 测试失败

了解测试的工作原理
让我们搞点破坏,然后看看会发生什么事。夜深了,我们决定把 "Hello World" 变成一个静态字符串。在更改期间,我们 不小心打错了字母,将 "o" 变成了 "0",如清单 6 所示。
清单 6. Hello world 类更改
2 public class HelloWorld {
3 private final static String HELLO_WORLD = "Hell0 World";
4 public String sayHello() {
5 return HELLO_WORLD;
6 }
7 }
在构建包时,我们看到了错误。清单 7 显示了 runtest 中的错误。它显示了失败的测试类和测试方法,并说明了为什么会失败。我们返回到代码中,改正错误后离开。
清单 7. 构建错误示例
2 Searching for build.xml ...
3 Buildfile: E:\projects\sample\build.xml
4 JUNIT:
5 compile:
6 jar:
7 compiletests:
8 runtests:
9 [junit] ..F
10 [junit] Time: 0
11 [junit]
12 [junit] FAILURES!!!
13 [junit] Test Results:
14 [junit] Run: 2 Failures: 1 Errors: 0
15 [junit] There was 1 failure:
16 [junit] 1) testSayHello(test.com.company.HelloWorldTest) "expected:<Hello
17 World> but was:<Hell0 World>"
18 [junit]
19 BUILD FAILED
20 E:\projects\sample\build.xml:35: Java returned: -1
21 Total time: 0 seconds
22
并非完全无痛
新的过程并不是完全无痛的。为使单元测试成为开发的一部分,您必须采取以下几个步骤:
下载和安装 JUnit。
下载和安装 Ant。
为构建创建单独的结构。
实现与主类分开的测试类。
学习 Ant 构建过程。
但好处远远超过了痛苦。通过使单元测试成为开发过程的一部分,您可以:
自动验证以捕捉更改“臭虫”
从接口角度设计类
提供干净的示例
在发行包中避免代码混乱和类膨胀。