利用 Ant 和 JUnit 进行增量开发.docx
- 文档编号:10432696
- 上传时间:2023-02-11
- 格式:DOCX
- 页数:18
- 大小:78.38KB
利用 Ant 和 JUnit 进行增量开发.docx
《利用 Ant 和 JUnit 进行增量开发.docx》由会员分享,可在线阅读,更多相关《利用 Ant 和 JUnit 进行增量开发.docx(18页珍藏版)》请在冰豆网上搜索。
利用Ant和JUnit进行增量开发
软件开发习惯中一个细微更改都可能会对软件质量产生巨大改进。
将单元测试合并到开发过程中,然后从长远角度来看它可以节省多少时间和精力。
本文通过使用代码样本说明了单元测试的种种好处,特别是使用Ant和JUnit带来的各种方便。
·
测试是大型开发过程中的基本原则之一。
在任何职业中,验证都是一个重要部分。
医生要通过验血来确诊。
波音公司在研制777的过程中对飞机的每个组件都进行了精心测试。
为什么软件开发就应该例外呢?
以前,由于在应用程序中将GUI和商业逻辑紧密联系在一起,这就限制了创建自动测试的能力。
当我们学会通过抽象层将商业逻辑从界面中分离出来时,各个单独代码模块的自动测试就替代了通过GUI进行的手工测试。
现在,集成开发环境(IDE)能在您输入代码的同时显示错误,对于在类中快速查找方法具有智能探测功能,可以利用语法结构生成彩色代码,而且具有许多其它功能。
因此,在编译更改过的代码之前,您已经全盘考虑了将构建的类,但您是否考虑过这样的修改会破坏某些功能呢?
每个开发者都碰到过更改“臭虫”。
代码修改过程可能会引入“臭虫”,而如果通过用户界面手工测试代码的话,在编译完成之前是不会发现它的。
然后,您就要花费几天的时间追踪由更改所引起的错误。
最近在我做的一个项目中,当我把后端数据库由Informix更改到Oracle时就遇到了这种情况。
大部分更改都十分顺利,但由于数据库层或使用数据库层的系统缺少单元测试,从而导致将大量时间花费在尝试解决更改“臭虫”上。
我花了两天的时间查到别人代码中的一个数据库语法更改。
(当然,那个人仍是我的朋友。
)
尽管测试有许多好处,但一般的程序员对测试都不太感兴趣,开始时我也没有。
您听到过多少次“它编译了,所以它一定能用”这种言论?
但“我思,故我在”这种原则并不适用于高质量软件。
要鼓励程序员测试他们的代码,过程必须简单无痛。
本文从某人学习用Java语言编程时所写的一个简单的类开始。
然后,我会告诉您我是如何为这个类编写单元测试,以及在编写完它以后又是如何将单元测试添加到构建过程中的。
最后,我们将看到将“臭虫”引入代码时发生的情况。
从一个典型类开始
第一个典型的Java程序一般都包含一个打印"HelloWorld"的main()。
在清单1中,我创建了一个HelloWorld对象的实例并调用sayHello()方法,该方法会打印这句习惯说法。
清单1.我的第一个Java应用程序"Helloworld"
/*
*HelloWorld.java
*Myfirstjavaprogram
*/
classHelloWorld{
/**
*Print"HelloWorld"
*/
voidsayHello(){
System.out.println("HelloWorld");
}
/**
*Test
*/
publicstaticvoidmain(String[]args){
HelloWorldworld=newHelloWorld();
world.sayHello();
}
}
main()方法是我的测试。
哦噢!
我将代码、文档、测试和样本代码包含在了一个模块中。
保佑Java!
但随着程序越变越大,这种开发方法很快就开始显现出了缺陷:
∙混乱
类接口越大,main()就越大。
类可能仅仅因为正常的测试而变得非常庞大。
∙代码膨胀
由于加入了测试,所以产品代码比所需要的要大。
但我不想交付测试,而只想交付产品。
∙测试不可靠
既然main()是代码的一部分,main()就对其他开发者通过类接口无法访问的私有成员和方法享有访问权。
出于这个原因,这种测试方法很容易出错。
∙很难自动测试
要进行自动测试,我仍然必须创建另一程序来将参数传递给main()。
类开发
对我来说,类开发是从编写main()方法开始的。
我在编写main()的时候就定义类和类的用法,然后实现接口。
它的一些明显的缺陷也开始显现出来。
一个缺陷是我传递给main()来执行测试的参数个数。
其次,main()本身在进行调用子方法、设置代码等操作时变得很混乱。
有时main()会比类实现的其余部分还要大。
更简单的过程
我原来的做法有一些很明显的缺陷。
因此,让我们看看有什么别的方法可以使问题简化。
我仍然通过接口设计代码并给出应用示例,正如原来的main()一样。
不同的是我将代码放到了另一个单独的类中,而这个类恰好是我的“单元测试”。
这种技术有以下几点好处:
∙设计类的一种机制
因为是通过接口进行开发,所以不太可能利用类的内部功能。
但因为我是目标类的开发者,我有到其内部工作的“窗口”,所以测试并不是个真正的黑箱。
仅凭这一点就足够推断出需要开发者本人在编写目标类的同时负责测试的开发,而不是由其他任何人代劳。
∙类用法的示例
通过将示例从实现中分离出来,开发者可以更快地提高速度,而且再不用在源代码上纠缠不清。
这种分离还有助于防止开发者利用类的内部功能,因为这些功能将来可能已经不存在了。
∙没有类混乱的main()
我不再受到main()的限制了。
以前我得将多个参数传递给main()来测试不同的配置。
现在我可以创建许多单独的测试类,每一个都维护各自的设置代码。
接下来我们将这个单独的单元测试对象放入构建过程中。
这样,我们就可以提供自动确认过程的方法。
∙确保所做的任何更改都不会对其他人产生不利影响。
∙我们在进行源码控制之前就可以测试代码,而无需等待汇编测试或在夜晚进行的构建测试。
这有助于尽早捕捉到“臭虫”,从而降低产生高质量代码的成本。
∙通过提供增量测试过程,我们提供了更好的实现过程。
如同IDE帮助我们在输入时捕捉到语法或编译“臭虫”一样,增量单元测试也帮助我们在构建时捕捉到代码更改“臭虫”。
回页首
使用JUnit自动化单元测试
要使测试自动化,您需要一个测试框架。
您可以自己开发或购买,也可以使用某些开放源代码工具,例如JUnit。
我选择JUnit出于以下几个原因:
∙不需要编写自己的框架。
∙它是开放源代码,因此不需要购买框架。
∙开放源代码社区中的其他开发者会使用它,因此可以找到许多示例。
∙它可以让我将测试代码与产品代码分开。
∙它易于集成到我的构建过程中。
测试布局
图1显示了使用样本TestSuite的JUnitTestSuite布局。
每个测试都由若干单独的测试案例构成。
每个测试案例都是一个单独的类,它扩展了TestClass类并包含了我的测试代码,即那些曾在main()中出现的代码。
在该例中,我向TestSuite添加了两个测试:
一个是SkeletonTest,我将它用作所有新类和HelloWorld类的起点。
图1.TestSuite布局
测试类HelloWorldTest.java
按照约定,测试类的名称中包含我所测试的类的名称,但将Test附加到结尾。
在本例中,我们的测试类是HelloWorldTest.java。
我复制了SkeletonTest中的代码,并添加了testSayHello()来测试sayHello()。
请注意HelloWorldTest扩展了TestCase。
JUnit框架提供了assert和assertEquals方法,我们可以使用这些方法来进行验证。
HelloWorldTest.java显示在清单2中。
清单2.HelloWorldTest.java
packagepany;
importpany.HelloWorld;
importjunit.framework.TestCase;
importjunit.framework.AssertionFailedError;
/**
*JUnit3.2testcasesforHelloWorld
*/
publicclassHelloWorldTestextendsTestCase{
publicHelloWorldTest(Stringname){
super(name);
}
publicstaticvoidmain(Stringargs[]){
junit.textui.TestRunner.run(HelloWorldTest.class);
}
publicvoidtestSayHello(){
HelloWorldworld=newHelloWorld();
assert(world!
=null);
assertEquals("HelloWorld",world.sayHello());
}
}
testSayHello()看上去和HelloWorld.java中原来的main方法类似,但有一个主要的不同之处。
它不是执行System.out.println并显示结果,而是添加了一个assertEquals()方法。
如果两个值不同,assertEquals将打印出两个输入的值。
您可能已经注意到这个方法不起作用!
HelloWorld中的sayHello()方法不返回字符串。
如果我先写过测试,就会捕捉到这一点。
我将"HelloWorld"字符串与输出流联结起来。
这样,按照清单3中显示的那样重写了HelloWorld,去掉main(),并更改了sayHello()的返回类型。
清单3.Helloworld测试案例。
packagepany;
publicclassHelloWorld{
publicStringsayHello(){
return"HelloWorld";
}
}
如果我保留了main()并修改了联系,代码看上去如下:
publicstaticvoidmain(String[]args){
HelloWorldworld=newHelloWorld();
System.out.println(world.sayHello());
}
新的main()与我测试程序中的testSayHello()非常相似。
是的,它看上去不象是一个现实世界中的问题(这是人为示例的问题),但它说明了问题。
在单独的应用程序中编写main()可以改进您的设计,同时帮助您设计测试。
现在我们已经创建了一个测试类,让我们使用Ant来将它集成到构建中。
回页首
使用Ant将测试集成到构建中
JakartaProject将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.AntXML构建图
与经典make实用程序的相似性是非常显著的。
这是理所当然的,因为make就是make。
但也要记住有一些差异:
通过Java实现的跨平台和可扩展性,通过XML实现的可配置,还有开放源代码。
下载和安装Ant
首先下载Ant(请参阅参考资料)。
将Ant解压缩到tools目录,再将Antbin目录添加到路径中。
(在我的机器上是e:
\tools\ant\bin。
)设置ANT_HOME环境变量。
在NT中,这意味着进入系统属性,然后以带有值的变量形式添加ANT_HOME。
ANT_HOME应该设置为Ant根目录,即包含bin和lib目录的目录。
(对我来说,是e:
\tools\ant。
)确保JAVA_HOME环境变量设置为安装了JDK的目录。
Ant文档有关于安装的详细信息。
下载和安装JUnit
下载JUnit3.2(请参阅参考资料)。
解开junit.zip,并将junit.jar添加到CLASSPATH。
如果将junit.zip解包到类路径中,可以通过运行以下命令来测试安装:
javajunit.textui.TestRunnerjunit.samples.AllTests
定义目录结构
在开始我们的构建和测试过程之前,需要一个项目布局。
图3显示了我的样本项目的布局。
下面描述了布局的目录结构:
∙build--类文件的临时构建位置。
构建过程将创建这个目录。
∙src--源代码的位置。
Src被分为test文件夹和main文件夹,前者用于所有的测试代码,而后者包含可交付的代码。
将测试代码与主要代码分离提供了几点特性。
首先,使主要代码中的混乱减少。
其次,它允许包对齐。
我就热衷与将类和与其相关的包放置在一起。
测试就应该和测试在一起。
它还有助于分发过程,因为你不可能打算将单元测试分发给客户。
在实际中,我们有多个目录,例如distribution和documentation。
我们还会在main下有多个用于包的目录,例如pany.util。
因为目录结构经常变动,所以在build.xml中有这些变动的全局字符串常数是很重要的。
图3.项目布局图
Ant构建配置文件示例
下一步,我们要创建配置文件。
清单4显示了一个Ant构建文件示例。
构建文件中的关键就是名为runtests的目标。
这个目标进行分支判断并运行外部程序,其中外部程序是前面已安装的junit.textui.TestRunner。
我们指定要使用语句pany.AllJUnitTests来运行哪个测试套件。
清单4.构建文件示例
basedir="${build.dir}"includes="com/**"/> taskname="junit"failonerror="true"> 运行Ant构建示例 开发过程中的下一步是运行将创建和测试HelloWorld类的构建。 清单5显示了构建的结果,其中包括了各个目标部分。 最酷的那部分是runtests输出语句: 它告诉我们整个测试套件都正确运行了。 我在图4和图5中显示了JUnitGUI,其中所要做的就是将runtest目标从junit.textui.TestRunner改为junit.ui.TestRunner。 当您使用JUnit的GUI部分时,您必须选择退出按钮来继续构建过程。 如果使用JunitGUI构建包,那么它将更难与大型的构建过程相集成。 另外,文本输出也与构建过程更一致,并可以定向输出到一个用于主构建记录的文本文件。 这对于每天晚上都要进行的构建非常合适。 清单5.构建输出示例 E: \projects\sample>antruntests Searchingforbuild.xml... Buildfile: E: \projects\sample\build.xml JUNIT: compile: [mkdir]Createddir: E: \projects\sample\build\classes [javac]Compiling1sourcefiletoE: \projects\sample\build\classes jar: [mkdir]Createddir: E: \projects\sample\build\lib [jar]Buildingjar: E: \projects\sample\build\lib\sample.jar compiletests: [mkdir]Createddir: E: \projects\sample\build\testcases [javac]Compiling3sourcefilestoE: \projects\sample\build\testcases runtests: [junit].. [junit]Time: 0.031 [junit] [junit]OK(2tests) [junit] BUILDSUCCESSFUL Totaltime: 1second 图4.JUnitGUI测试成功 图5.JUnitGUI测试失败 回页首 了解测试的工作原理 让我们搞点破坏,然后看看会发生什么事。 夜深了,我们决定把"HelloWorld"变成一个静态字符串。 在更改期间,我们不小心打错了字母,将"o"变成了"0",如清单6所示。 清单6.Helloworld类更改 packagepany; publicclassHelloWorld{ privatefinalstaticStringHELLO_WORLD="Hell0World"; publicStringsayHello(){ returnHELLO_WORLD; } } 在构建包时,我们看到了错误。 清单7显示了runtest中的错误。 它显示了失败的测试类和测试方法,并说明了为什么会失败。 我们返回到代码中,改正错误后离开。 清单7.构建错误示例 E: \projects\sample>antruntests Searchingforbuild.xml... Buildfile: E: \projects\sample\build.xml JUNIT: compile: jar: compiletests: runtests: [junit]..F [junit]Time: 0 [junit] [junit]FAILURES! ! ! [junit]TestResults: [junit]Run: 2Failures: 1Errors: 0 [junit]Therewas1failure: [junit]1)testSayHello(pany.HelloWorldTest)"expected: World>butwas: [junit] BUILDFAILED E: \projects\sample\build.xml: 35: Javareturned: -1 Totaltime: 0seconds 回页首 并非完全无痛 新的过程并不是完全无痛的。 为使单元测试成为开发的一部分,您必须采取以下几个步骤: 1.下载和安装JUnit
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 利用 Ant JUnit 进行增量开发 进行 增量 开发