6.1初识JUnit测试框架
单元级测试在面向对象的开发中变得越来越重要,而一个简明易学、适用广泛、高效稳定的单元级测试框架对成功的实施测试有着至关重要的作用。在java编程环境中,Junit Framework是一个已经被多数java程序员采用和实证的优秀的测试框架。开发人员只需要按照Junit的约定编写测试代码,就可以对自己要测试的代码进行测试。本小节我们采用Junit的一个小例子来学习在Myeclipse下Junit的运用。
我们创建一个Java工程,项目名称为JunitLesson1,添加一个example.Hello类,首先我们给Hello类添加一个abs()方法,作用是返回绝对值,源代码如下:
package example;
public class Hello {
public int reAbs(int a) {
return a > 0 ? a : -a;
}
}
因为MyEclipse现在默认的是Junit3.8,我们准备使用Junit4.4,所以必须在项目里面导入Junit4包。选择project->properties,在左侧树形条里面选择"java build path",单击右边的Libraries选项卡,如下图所示,点击"add External JARs..",找到Junit4.4.jar,将他导入到项目内。
图6-1 引入Junit包
下一步,我们在项目 JunitLesson1根目录下添加一个新目录 testsrc,并把它加入到项目源代码目录中:单击图3-1的Source选项卡,新建一个目录test,单元测试的代码放在此目录下。
我们准备对这个方法进行测试,确保功能正常。选中Hello.java,右键点击,选择New->other 在弹出的对话框中选择java->JUnit->JUnit Test Case 点击Next按钮,弹出如图6-2对话框:
图6-2 建立Test Case
默认选项,点击Next按钮,进入图6-3,选择要进行测试的方法,把Hello类的reAbs方法勾选上,点击Finish按钮,系统自动生成对Hello类的测试类HelloTest,源代码如下:
图6-3 建立Test 方法
package example;
import static org.junit.Assert.*;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class HelloTest {
@Before //Java Anotation 注释性编程,
public void setUp() throws Exception {
}
@After
public void tearDown() throws Exception {
}
@Test
public void testReAbs() {
fail("Not yet implemented");
}
}
修改如下:
package example;
import static org.junit.Assert.*;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class HelloTest {
private Hello hello;
@Before
public void setUp() throws Exception {
hello = new Hello();
}
@After
public void tearDown() throws Exception {
}
@Test
public void testReAbs() {
int a1 = hello.reAbs(20);
assertEquals(20, a1);// ――――――#1
}
}
然后运行HelloTest类查看运行结果,如下图所示:
图6-4 Errors=0,Failures=0,绿色条代表测试通过。
将1处代码修改如下:
assertEquals(-20, a1);
再次运行HelloTest类查看运行结果,如下图所示:
图6-5 Errors=0,Failures=1,红条代表有测试方法没有通过。
在HelloTest类中,@Before注释的方法是建立测试环境,这里创建一个Hello类的实例;@After注释的方法用于清理资源,如释放打开的文件等等。以@Test注释的方法被认为是测试方法,JUnit会依次执行这些方法。在testReAbs()方法中,我们对reAbs()的测试分别2次测试,assertEquals方法比较运行结果和预期结果是否相同。如果有多个测试方法,JUnit会创建多个测试实例,每次运行一个测试方法,@Before和@After注释的方法会在测试实例前后被调用,因此,不要在一个testA()中依赖testB()。
现在我们在Hello类中增加一个方法,代码如下:
public double add(double number1, double number2) {
return number1 + number2;
}
然后在HelloTest类中增加一个测试方法Test Add,代码如下:
@Test
public void testAdd(){
double result=hello.add(10, 30);
assertEquals(40, result,0);
}
运行HelloTest类查看运行结果。AssertEquals方法中的第三个参数表示的是允许误差范围。
6.2 Junit框架分析
自动化测试框架就是可以自动对代码进行单元测试的框架。在传统的软件开发流程中,需求,设计,编码和测试都有各自独立的阶段,阶段之间不可以回溯,所以测试是不是自动化并不重要。新的软件开发流程中,引入了迭代开发的概念,并且项目迭代周期短,对代码要进行频繁的重构,这就要求单元级测试必须能够自动、简便、高速的运行,否则重构就是不现实的。
自动化测试框架应该支持简单操作,向测试包中添加新的测试用例,而且不影响测试包的正常运行。Junit的自动化测试框架图如下:
图3-6 Junit自动化测试框架
这是一个典型的Composite设计模式:TestSuite可以容纳任何派生自Test的对象;当调用TestsSuite对象的run()方法时,它会遍历自己容纳的对象,逐个调用它们的run()方法。使用者无须关心自己拿到的究竟是TestCase还是TestSute,只管调用对象的run()方法,然后分析run()方法返回的结果就行了
TestCase (测试用例)——扩展了JUnit的TestCase类的类。它以testXXX方法的形式包含一个或多个测试。一个test case把具有公共行为的测试归入一组。在本书的后续部分,当我们提到测试的时候,我们指的是一个testXXX方法;当我们提及test case的时候,我们指的是一个继承自TestCase的类,也就是一组测试。
TestSuite(测试集合)——一组测试。一个test suite是把多个相关测试归入一组的便捷方式。例如,如果你没有为TestCase定义一个test suite,那么JUnit就会自动提供一个test suite,包含TestCase中所有的测试(本书稍后会细说)。
TestRunner(测试运行器)——执行test suite的程序。JUnit提供了几个test runner,你可以用它们来执行你的测试。没有TestRunner接口,只有一个所有test runner都继承的BaseTestRunner。因此,当我们编写TestRunner的时候,我们实际上指的是任何继承 BaseTestRunner的test runner类。
图3-7 JUnit成员三重唱,共同产生测试结果
这3 个类是JUnit框架的骨干。一旦你理解了TestCase、TestSuite和BaseTestRunner的工作方式,你就可以随心所欲地编写测试 了。在正常情况下,你只需要编写test case,其他类会在幕后帮你完成测试。这3个类和另外4个类紧密配合,形成了JUnit框架的核心。图3-8归纳了这7个核心类各自的责任。
图3-8 Junit核心类及接口
6.3 用TestCase来工作
概括地说,JUnit的工作过程就是由TestRunner来运行包含一个或多个TestCase(或者其他TestSuite)的TestSuite。但在常规工作中,你通常只和TestCase打交道。有些测试会用到一些资源,要把这些资源配置好是件麻烦事。比如像数据库连接这样的资源就是典型的例子。可能TestCase中的几个测试需要连接到一个测试数据库并访问一些测试表。另一组测试则可能需要复杂的数据结构或者很长的随机输入序列。
把通用的资源配置代码放在测试中并不是好主意。你并不想测试自己建立资源的能力,你只需要一个稳健的外部环境,在这个环境中运行测试。运行测试所需要的这个外部资源环境通常称作test fixture。
定义 fixture——运行一个或多个测试所需的公用资源或数据集合。
TestCase 通过setUp和tearDown方法来自动创建和销毁fixture。TestCase会在运行每个测试之前调用setUp,并且在每个测试完成之后调 用tearDown。把不止一个测试方法放进同一个TestCase的一个重要理由就是可以共享fixture代码。图3-9描述了TestCase的生 命周期。
需 要fixture的一个典型例子就是数据库连接。如果一个TestCase包括好几项数据库测试,那么它们都需要一个新建立的数据库连接。通过 fixture就可以很容易地为每个测试开启一个新连接,而不必重复编写代码。你还可以用fixture来生成输入文 件,这意味着你不必在测试中携带测试文件,而且在测试开始执行之前状态总是已知的。
JUnit还通过Assert接口提供的工具方法来复用代码。我们将在下一节中加以介绍。
图3-9 TestCase生命周期,为每个测试方法重新创建fixture
在测试一个单元方法时,有时您会需要给它一些对象作为运行时的资料,例如您撰写下面这个测试案例:
MaxMinTest.java
package onlyfun.caterpillar.test; import onlyfun.caterpillar.MaxMinTool; import junit.framework.TestCase; public class MaxMinTest extends TestCase { public void testMax() { int[] arr = {-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5}; assertEquals(5, MaxMinTool.getMax(arr)); } public void testMin() { int[] arr = {-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5}; assertEquals(-5, MaxMinTool.getMin(arr)); } }
您将设计的MaxMinTool包括静态方法getMax()与getMin(),当您给它一个整数阵列,它们将个别传回阵列中的最大值与最小值,显然的,您所准备的阵列重复出现在两个单元测试之中,重复的程式码在设计中可以减少就尽量减少,在这两个单元测试中,整数阵列的准备是单元方法所需要的资源,我们称之为fixture,也就是一个测试时所需要的资源集合。
fixture必须与上下文(Context)无关,也就是与程式执行前后无关,这样才符合单元测试的意涵,为此,通常将所需的fixture撰写在单元方法之中,如此在单元测试开始时创建fixture,并于结束后销毁fixture。
然而对于重复出现在各个单元测试中的fixture,您可以集中加以管理,您可以在继承TestCase之后,重新定义setUp()与tearDown()方法,将数个单元测试所需要的fixture在setUp()中创建,并在tearDown()中销毁,例如:
MaxMinTest.java
package onlyfun.caterpillar.test; import onlyfun.caterpillar.MaxMinTool; import junit.framework.TestCase; public class MaxMinTest extends TestCase { private int[] arr; protected void setUp() throws Exception { super.setUp(); arr = new int[]{-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5}; } protected void tearDown() throws Exception { super.tearDown(); arr = null; } public void testMax() { assertEquals(5, MaxMinTool.getMax(arr)); } public void testMin() { assertEquals(-5, MaxMinTool.getMin(arr)); } }
setUp()方法会在每一个单元测试testXXX()方法开始前被呼叫,因而整数阵列会被建立,而tearDown()会在每一个单元测试 testXXX()方法结束后被呼叫,因而整数阵列参考名称将会参考至null,如此一来,您可以将fixture的管理集中在 setUp()与tearDown()方法之后。最后按照测试案例的内容,您完成MaxMinTool类别:
MaxMinTool.java
package onlyfun.caterpillar; public class MaxMinTool { public static int getMax(int[] arr) { int max = Integer.MIN_VALUE; for(int i = 0; i < arr.length; i++) { if(arr[i] > max) max = arr[i]; } return max; } public static int getMin(int[] arr) { int min = Integer.MAX_VALUE; for(int i = 0; i < arr.length; i++) { if(arr[i] < min) min = arr[i]; } return min; } }
Swing介面的TestRunner在测试失败时会显示红色的棒子,而在测试成功后会显示绿色的棒子,而 "Keep the bar green to keep the code clean." 正是JUnit的名言,也是测试的最终目的。
本文暂时没有评论,来添加一个吧(●'◡'●)