技术开发 频道

利用 Ant 和 JUnit 进行增量开发

  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. 构建文件示例

1 <property name="app.name"   value="sample" />
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. 构建输出示例

1 E:\projects\sample>ant runtests
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 类更改

1 package com.company;
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. 构建错误示例

1 E:\projects\sample>ant runtests
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 构建过程。

         但好处远远超过了痛苦。通过使单元测试成为开发过程的一部分,您可以:

         自动验证以捕捉更改“臭虫”

         从接口角度设计类

         提供干净的示例

         在发行包中避免代码混乱和类膨胀。

0
相关文章