计算机系统应用教程网站

网站首页 > 技术文章 正文

JUnit测试框架,程序员的好助手,不要只把属性值输出而已

btikc 2024-09-02 17:15:41 技术文章 9 ℃ 0 评论

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的名言,也是测试的最终目的。

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表