JUnit 使用手册


Junit 使用 JUnit是由 Erich Gamma 和 Kent Beck 编写的一个回归测试框架 (regression testing framework),供Java开发人员编写单元测试之用。 JUnit 是一个开发源代码的 Java 测试框架,用于编写和运行可重复的测试。 他是用于单元测试框架体系 xUnit 的一个实例(用于 java 语言)。它包括以下 特性: 1、用于测试期望结果的断言(Assertion) 2、用于共享共同测试数据的测试工具 3、用于方便的组织和运行测试的测试套件 4、图形和文本的测试运行器 需要说明的是 junit 一般是用来进行单元测试的,因此需要了解被测试代码 的内部结构(即所谓的白盒测试),另外 junit 是在 xp 编程和重构(refactor) 中被极力推荐使用的工具,因为在实现自动单元测试的情况下可以大大的提高开 发的效率,但是实际上编写测试代码也是需要耗费很多的时间和精力的,那么使 用这个东东好处到底在哪里呢?笔者认为是这样的: 1、对于 xp 编程而言,要求在编写代码之前先写测试,这样可以强制你在写 代码之前好好的思考代码(方法)的功能和逻辑,否则编写的代码很不稳定,那 么你需要同时维护测试代码和实际代码,这个工作量就会大大增加。因此在 xp 编程中,基本过程是这样的:构思-》编写测试代码-》编写代码-》测试,而 且编写测试和编写代码都是增量式的,写一点测一点,在编写以后的代码中如果 发现问题可以较块的追踪到问题的原因,减小回归错误的纠错难度 2、对于重构而言,其好处和 xp 编程中是类似的,因为重构也是要求改一点 测一点,减少回归错误造成的时间消耗。 3、对于非以上两种情况,我们在开发的时候使用 junit 写一些适当的测试 也是有必要的,因为一般我们也是需要编写测试的代码的,可能原来不是使用的 junit,如果使用 junit,而且针对接口(方法)编写测试代码会减少以后的维 护工作,例如以后对方法内部的修改(这个就是相当于重构的工作了)。另外就 是因为 junit 有断言功能,如果测试结果不通过会告诉我们那个测试不通过,为 什么,而如果是想以前的一般做法是写一些测试代码看其输出结果,然后再由自 己来判断结果使用正确,使用junit 的好处就是这个结果是否正确的判断是它来 完成的,我们只需要看看它告诉我们结果是否正确就可以了,在一般情况下会大 大提高效率。 1、概述 Junit 测试是程序员测试,即所谓白盒测试,因为程序员知道被测试的软件 如何(How)完成功能和完成什么样(What)的功能。 Junit 本质上是一套框架,即开发者制定了一套条条框框,遵循这此条条框 框要求编写测试代码,如继承某个类,实现某个接口,就可以用 Junit 进行自动 测试了。 由于 Junit 相对独立于所编写的代码,可以测试代码的编写可以先于实现代 码的编写,XP 中推崇的 test first design 的实现有了现成的手段:用 Junit 写测试代码,写实现代码,运行测试,测试失败,修改实现代码,再运行测试, 直到测试成功。以后对代码的修改和优化,运行测试成功,则修改成功。 Java 下的 team 开发,采用 cvs(版本控制) + ant(项目管理) + junit(集 成测试) 的模式时,通过对 ant 的配置,可以很简单地实现测试自动化。 对不同性质的被测对象,如 Class,Jsp,Servlet,Ejb 等,Junit 有不同 的使用技巧。以下以 Class 测试为例讲解,除非特殊说明。 2、下载安装 • 去Junit主页下载最新版本 3.8.1 程序包junit-3.8.1.zip • 用 winzip 或 unzip 将 junit-3.8.1.zip 解压缩到某一目录名为 $JUNITHOME • 将 junit.jar 和$JUNITHOME/junit 加入到 CLASSPATH 中,加入后者只因 为测试例程在那个目录下。 • 注意不要将 junit.jar 放在 jdk 的 extension 目录下 java junit.swingui.TestRunner junit.samples.AllTests 3、Junit 架构 run() << interface >> Test fName setUp() runTest() tearDown() run() TestCase run() addTest() TestSuite * TestResult junit.framework assertTrue() assertEquals() ... Assert junit.textui.TestRunner junit.swingui.TestRunner 下面以 Money 这个类为例进行说明。 public class Money { private int fAmount;//余额 private String fCurrency;//货币类型 public Money(int amount, String currency) { fAmount= amount; fCurrency= currency; } public int amount() { return fAmount; } public String currency() { return fCurrency; } public Money add(Money m) {//加钱 return new Money(amount()+m.amount(), currency()); } public boolean equals(Object anObject) {//判断钱数是否相等 if (anObject instanceof Money) { Money aMoney= (Money)anObject; return aMoney.currency().equals(currency()) && amount() == aMoney.amount(); } return false; } } Junit 本身是围绕着两个设计模式来设计的:命令模式和集成模式. • 命令模式 利用 TestCase 定义一个子类,在这个子类中生成一个被测试的对象, 编写代码检测某个方法被调用后对象的状态与预期的状态是否一致,进而 断言程序代码有没有 bug。 当这个子类要测试不只一个方法的实现代码时,可以先建立测试基 础,让这些测试在同一个基础上运行,一方面可以减少每个测试的初始化, 而且可以测试这些不同方法之间的联系。 例如,我们要测试 Money 的 Add 方法,可以如下: public class MoneyTest extends TestCase { //TestCase 的子类 public void testAdd() { //把测试代码放在 testAdd 中 Money m12CHF= new Money(12, "CHF"); //本行和下一行进行 一些初始化 Money m14CHF= new Money(14, "CHF"); Money expected= new Money(26, "CHF");//预期的结果 Money result= m12CHF.add(m14CHF); //运行被测试的方法 Assert.assertTrue(expected.equals(result)); //判断 运行结果是否与预期的相同 } } 如果测试一下 equals 方法,用类似的代码,如下: public class MoneyTest extends TestCase { //TestCase 的子类 public void testEquals() { //把测试代码放在 testEquals 中 Money m12CHF= new Money(12, "CHF"); //本行和下一行进行 一些初始化 Money m14CHF= new Money(14, "CHF"); Assert.assertTrue(!m12CHF.equals(null));//进行不同情况 的测试 Assert.assertEquals(m12CHF, m12CHF); Assert.assertEquals(m12CHF, new Money(12, "CHF")); // ( 1) Assert.assertTrue(!m12CHF.equals(m14CHF)); } } 当要同时进行测试 Add 和 equals 方法时,可以将它们的各自的初始 化工作,合并到一起进行,形成测试基础,用 setUp 初始化,用 tearDown 清除。如下: public class MoneyTest extends TestCase {//TestCase 的子类 private Money f12CHF;//提取公用的对象 private Money f14CHF; protected void setUp() {//初始化公用对象 f12CHF= new Money(12, "CHF"); f14CHF= new Money(14, "CHF"); } public void testEquals() {//测试 equals 方法的正确性 Assert.assertTrue(!f12CHF.equals(null)); Assert.assertEquals(f12CHF, f12CHF); Assert.assertEquals(f12CHF, new Money(12, "CHF")); Assert.assertTrue(!f12CHF.equals(f14CHF)); } public void testSimpleAdd() {//测试 add 方法的正确性 Money expected= new Money(26, "CHF"); Money result= f12CHF.add(f14CHF); Assert.assertTrue(expected.equals(result)); } } 将以上三个中的任一个 TestCase 子类代码保存到名为 MoneyTest.java 的文件里,并在文件首行增加 import junit.framework.*; ,都是可以运行的。关于 Junit 运行的问题很有意思,下面单独说明。 上面为解释概念“测试基础(fixture)”,引入了两个对两个方法的 测试。命令模式与集成模式的本质区别是,前者一次只运行一个测试。 • 集成模式 利用 TestSuite 可以将一个 TestCase 子类中所有 test***()方法包 含进来一起运行,还可将TestSuite 子类也包含进来,从而行成了一种等 级关系。可以把 TestSuite 视为一个容器,可以盛放 TestCase 中的 test***()方法,它自己也可以嵌套。这种体系架构,非常类似于现实中 程序一步步开发一步步集成的现况。 对上面的例子,有代码如下: public class MoneyTest extends TestCase {//TestCase 的子类 .... public static Test suite() {//静态 Test TestSuite suite= new TestSuite();//生成一个 TestSuite suite.addTest(new MoneyTest("testEquals")); //加入测试 方法 suite.addTest(new MoneyTest("testSimpleAdd")); return suite; } } 从 Junit2.0 开始,有列简捷的方法: public class MoneyTest extends TestCase {//TestCase 的子类 .... public static Test suite() {静态 Test return new TestSuite(MoneyTest.class); //以类为参数 } } TestSuite 见嵌套的例子,在后面应用案例中有。 4、测试代码的运行 先说最常用的集成模式。 测试代码写好以后,可以相应的类中写 main 方法,用 java 命令直接运行; 也可以不写 main 方法,用Junit 提供的运行器运行。Junit提供了 textui,awtui 和 swingui 三种运行器。 以前面第 2 步中的 AllTests 运行为例,可有四种: java junit.textui.TestRunner junit.samples.AllTests java junit.awtui.TestRunner junit.samples.AllTests java junit.swingui.TestRunner junit.samples.AllTests java junit.samples.AllTests main 方法中一般也都是简单地用 Runner 调用 suite(),当没有 main 时, TestRunner 自己以运行的类为参数生成了一个 TestSuite. 对于命令模式的运行,有两种方法。 • 静态方法 TestCase test= new MoneyTest("simple add") { public void runTest() { testSimpleAdd(); } }; • 动态方法 TestCase test= new MoneyTest("testSimpleAdd"); import junit.framework.*; public class MoneyTest extends TestCase {//TestCase 的子类 private Money f12CHF;//提取公用的对象 private Money f14CHF; public MoneyTest(String name){ super(name); } protected void setUp() {//初始化公用对象 f12CHF= new Money(12, "CHF"); f14CHF= new Money(14, "CHF"); } public void testEquals() {//测试 equals 方法的正确性 Assert.assertTrue(!f12CHF.equals(null)); Assert.assertEquals(f12CHF, f12CHF); Assert.assertEquals(f12CHF, new Money(12, "CHF")); Assert.assertTrue(!f12CHF.equals(f14CHF)); } public void testAdd() {//测试 add 方法的正确性 Money expected= new Money(26, "CHF"); Money result= f12CHF.add(f14CHF); Assert.assertTrue(expected.equals(result)); } // public static void main(String[] args) { // TestCase test=new MoneyTest("simple add") { // public void runTest() { // testAdd(); // } // }; // junit.textui.TestRunner.run(test); // } public static void main(String[] args) { TestCase test=new MoneyTest("testAdd"); junit.textui.TestRunner.run(test); } } 再给一个静态方法用集成测试的例子: public static Test suite() { TestSuite suite= new TestSuite(); suite.addTest( new testCar("getWheels") { protected void runTest() { testGetWheels(); } } ); suite.addTest( new testCar("getSeats") { protected void runTest() { testGetSeats(); } } ); return suite; } junit 的使用并不很难,然而要书写一个好的 TestCase 却并非易事。一个不好 的 TestCase 往往是既浪费了时间,也起不了实际的作用。相反,一个好的 TestCase,不仅可以很好的指出代码中存在的问题,而且也可以作为代码更准确 的文档,同时还在持续集成的过程中起非常重要的作用。在此给出书写TestCase 时需要注意的几点: 测试的独立性:一次只测试一个对象,方便定位出错的位置。这有 2 层意思:一个 TestCase,只测试一个对象;一个 TestMethod,只测试这个对象 中的一个方法。 给测试方法一个合适的名字。 在 assert 函数中给出失败的原因,如:assertTrue( “… should be true”, ……),方便查错。在这个例子中,如果无法通过 assertTrue,那么 给出的消息将被显示。在 junit 中每个 assert 函数都有第一个参数是出错时显 示消息的函数原型。 测试所有可能引起失败的地方,如:一个类中频繁改动的函数。对于那些 仅仅只含有 getter/setter 的类,如果是由 IDE(如 Eclipse)产生的,则可不 测;如果是人工写,那么最好测试一下。 在 setUp 和 tearDown 中的代码不应该是与测试方法相关的,而应该是全局相 关的。如针对与测试方法 A 和 B,在 setUp 和 tearDown 中的代码应该是 A 和 B 都需要的代码。 Eclipce 中集成 junit 运行 Eclipse。新建一个 workplace 项目,点击文件->新建->项目,选择 Java 项目,点击下一步。起一个项目名称,例如 ProjectWithJUnit。 点击完成。这样就完成新项目的建立了。再来配置一下 Eclipse, 在构建路径中添加 JUnit 类库。在工具条上点击项目->属性,选择 Java 构建路 径,库,选择添加外部 JAR,浏览 Junit 被存储的目录,选择 junit.jar,点击 打开。你将会看见 JUnit 出现在库的列表中。点击确定,让 Eclipse 重建路径。 现在开发我们的“Hello World”例子。按照 TDD 的规则,应该在代码建立以前 先把测试写好。 为了能够在某出开始,我们假设未来的类名是 HelloWorld,并且有一个方法 Say(), 这个方法返回 String 的值(例如“Hello World!”)。 建立测试,在 ProjectWithJUnit 的标题上面点击右键,选择新建->其他, 展开“Java”选项,选择 JUnit。在右边的栏目对话框中选择测试案例,然后下 一步。参考图 1。 图 1. 在 Eclipse 中建立 JUnit 测试 在测试类这一栏中,写上将要被测试的类名 HelloWorld。 选择一个测试案例的名字,例如 TestThatWeGetHelloWorldPrompt(是的,看上 去很长,但是很清楚它的行为。)点击完成。 TestThatWeGetHelloWorldPrompt 的代码如下: import junit.framework.TestCase; public class TestThatWeGetHelloWorldPrompt extends TestCase { public TestThatWeGetHelloWorldPrompt( String name) { super(name); } public void testSay() { HelloWorld hi = new HelloWorld(); assertEquals("Hello World!", hi.say()); } public static void main(String[] args) { junit.textui.TestRunner.run( TestThatWeGetHelloWorldPrompt.class); } } 代码并不复杂;只是有点与众不同。然而,让我们考察一下细节。我们继承了 JUnit 的 TestCase 类,它在JUnit 的 javadocs 定义为“运行众多测试的夹具。” JUnit 也有 TestSuite 类,它是一组测试案例的集合,但在本文中不做讨论。 建立测试案例的步骤如下: 1、建立一个 junit.framework.TestCase 的实例。 2、定义一些以“test”开头的无返回方法(例如 testWasTransactionSuccessful(),testShow(),等等)。 TestThatWeGetHelloWorldPrompt.java 包含这些:TestCase 的子类和一个叫做 testSay()的方法。这个方法调用了 assertEquals()函数,它用来比较我们预期 的值和由 say()返回的值。 main()方法用来运行测试和显示输出的。JUnit 的 TestRunner 处理测试,提供 基于图像和文本的输出表现形式。我们使用基于文本的版本,因为Eclipse 支持 它,且也适合我们。当开始运行后,基于文本的版本测试会以文本形式输出, Eclipse 会把这些输出自动变成图像界面的输出。 按照 TDD 规范,首次运行测试,应该故意让它失败。点击运行->运行为->Junit 测试(记住 TestThatWeGetHelloWorldPrompt.java 应该被突出的显示在包资源 管理器中)。在左边窗口,应该看见 JUnit 窗口而不是包资源管理器,它显示一 个红条,一次失败的测试,具体的失败原因参看图 2。如果没有自动显示这些内 容,点击 JUnit 标签(在底部的左边)。 图 2. JUnit 中失败的测试 很好!但却失败了。现在我们来建立被测试代码:在包资源管理器窗口的 ProjectWithJUnit 标题上右击,选择新建->类。选择类名,我们已经假设了它 叫 HelloWorld,然后直接点击完成。为 HelloWorld.java 填入下列代码: public class HelloWorld { public String say() { return("Hello World!"); } } 这段代码很简单,甚至不需要注解,我们再来看看结果。按照上面描述过的方式, 在 JUnit 的窗口中显示了一个绿条,绿条证明测试成功。 现在,我们想再让测试失败一次,但原因不同。这有助于展示 JUnit 测试中不同 的报错信息。修改assertEquals()代码,把“Hello World!”变成“Hello Me!”。 当再次运行 JUnit 时,结果变成了红条,在 JUnit 窗口的底部输出了失败原因, 参看图 4。 图 4. JUnit 中的 ComparisonError 如何优化你的测试代码 “当你试图打印输出一些信息或调试一个表达式时,写一些测试代码来替代那 些传统的方法。”一开始,你会发现你总是要创建一些新的 Fixture,而且测试 似乎使你的编程速度慢了下来。然而不久之后,你会发现你重复使用相同的 Fixture,而且新的测试通常只涉及添加一个新的测试方法。 你可能会写许多测试代码,但你很快就会发现你设想出的测试只有一小部分是真 正有用的。你所需要的测试是那些会失败的测试,即那些你认为不会失败的测试, 或你认为应该失败却成功的测试。 我们前面提到过测试是一个不会中断的过程。一旦你有了一个测试,你就要 一直确保其正常工作,以检验你所加入的新的工作代码。不要每隔几天或最后才 运行测试,每天你都应该运行一下测试代码。这种投资很小,但可以确保你得到 可以信赖的工作代码。你的返工率降低了,你会有更多的时间编写工作代码。 不要认为压力大,就不写测试代码。相反编写测试代码会使你的压力逐渐减轻, 应为通过编写测试代码,你对类的行为有了确切的认识。你会更快地编写出有效 率地工作代码。下面是一些具体的编写测试代码的技巧或较好的实践方法: 1. 不要用 TestCase 的构造函数初始化 Fixture,而要用 setUp()和 tearDown() 方法。 2. 不要依赖或假定测试运行的顺序,因为JUnit 利用 Vector 保存测试方法。所 以不同的平台会按不同的顺序从 Vector 中取出测试方法。 3. 避免编写有副作用的 TestCase。例如:如果随后的测试依赖于某些特定的交 易数据,就不要提交交易数据。简单的会滚就可以了。 4. 当继承一个测试类时,记得调用父类的 setUp()和 tearDown()方法。 5. 将测试代码和工作代码放在一起,一边同步编译和更新。(使用 Ant 中有支 持 junit 的 task.) 6. 测试类和测试方法应该有一致的命名方案。如在工作类名前加上test 从而形 成测试类名。 7. 确保测试与时间无关,不要依赖使用过期的数据进行测试。导致在随后的维 护过程中很难重现测试。 8. 如果你编写的软件面向国际市场,编写测试时要考虑国际化的因素。不要仅 用母语的 Locale 进行测试。 9. 尽可能地利用 JUnit 提供地 assert/fail 方法以及异常处理的方法,可以使 代码更为简洁。 10.测试要尽可能地小,执行速度快。
还剩19页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 15 金币 [ 分享pdf获得金币 ] 2 人已下载

下载pdf

pdf贡献者

quanquanat

贡献于2012-01-05

下载需要 15 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf