分类:
2008-08-03 15:25:13
专栏:
【 IT168 技术文档 】单元测试是整个测试流程中最基础的部分,它们要求程序员尽可能早地发现问题,并给予控制,另外如果集成测试出现问题,它可以帮助诊断。这样就为在软件开发流程中建立高效的事件反应机制打下了坚实基础。
为什么需要单元测试
在开发软件的过程中,用户需要实际运行所编写的代码以确保程序的正确性。当软件变得越来越大,再去添加新的功能或做一些新的改动时,就很容易带来新的问题,甚至会使程序无法正常运行。然而要手动的运行代码,测试代码的可行性也是非常枯燥以及非常耗费时间的事情。
为了减少这种手动测试,可以通过创建单元测试来自动完成测试的工作。当修改代码或者添加新功能后,可以执行单元测试来保证代码运行无误。所有测试工作都是由单元测试自动完成的,开发人员所要做的就是停下来喝杯茶,看看程序的执行状态。
使用单元测试的另一个理由是实现测试驱动的开发,这在当前是比较流行的开发方式。测试驱动的开发尝试首先写出单元测试,然后完成实际的代码。通过单元测试来提供类的定义,当实际开始编写代码时,用户仅仅需要做的就是具体类的实现,只要单元测试运行通过,代码的实现也将告一段落了。写单元测试的同时,也在同时在做项目的设计,当项目结束后,单元测试还将是不错的文档,何乐而不为呢?
自信编码
人的记忆是短暂的,但代码的修改是无限的,怎样让无限的修改不会因为记忆的短暂而带来无穷的烦恼呢?这是非常矛盾的事情,单元测试能够一定程序上解决这个问题。
也许所有的程序员都遇到过这样的问题,当要修改很久以前的代码或他人编写的代码时,总是会很犹豫,因为他们不清楚所做的修改会不会引起其它的问题,只能当遍历了所有的代码后才敢动手。这是非常正常的,但也因此浪费了很多的时间,通过单元测试能够一定程度上增强用户的自信心,因为单元测试的前提假设就是,如果通过了所有的测试,代码就是可行的。
JUnit 测试框架
在 Java 语言中,可以通过 JUnit 框架进行单元测试, JUnit 是由“ Erich Gamma ”和“ Kent Beck ”创建的,他们也是在“设计模式”和“极限编程”领域最伟大的作者之一。
单元测试的实现是很简单的,可以认为它只是判断在某一个时刻,程序运行的值和预期的值是否一致,但在实际的应用的时候是很灵活的,在此介绍 JUnit 中的一些断言以及 JUnit 测试框架的使用,使读者能够快速的进入单元测试的领域,更快的进行开发。 断言
JUnit 提供了一些辅助函数,用于帮助开发人员确定某些被测试函数是否工作正常。通常而言,把所有这些函数统称为断言,断言是单元测试最基本的组成部分。
通常每种类型的断言都有两种形式,一种包含接收一个 message 参数,例如“ static public void assertTrue(String message, boolean condition) ”, message 表示出错时的提示信息,另外一个则没有 message 参数。
下面将分别介绍 JUnit 框架 Assert 类中的断言以及部分实现,每个函数的实现方法都为 Assert 类中定义的方法,读者也可以查看 JUnit 框架 Assert 类的实现代码。
assertEquals 断言
这是应用非常广泛的一个断言,它的作用是比较实际的值和用户预期的值是否一样, assertEquals 在 JUnit 中有很多不同的实现,以参数 expected 和 actual 都为 Object 类型的为例, assertEquals 定义为:
static public void assertEquals(String message, Object expected, Object actual) {
if (expected == null && actual == null )
return ;
if (expected != null && expected.equals(actual))
return ;
failNotEquals(message, expected, actual);
}
其中, expected 为用户期望某一时刻对象的值, actual 为某一时刻对象实际的值。如果这两值相等的话(通过对象的 equals 方法比较),说明预期是正确的,也就是说,代码运行是正确的。 assertEquals 还提供了其它的一些实现,例如整数比较,浮点数的比较等等。
assertTrue 与 assertFalse 断言
assertTrue 与 assertFalse 可以判断某个条件是真还是假,如果和预期的值相同则测试成功,否则将失败, assertTrue 的定义如下:
static public void assertTrue(String message, boolean condition) {
if (!condition)
fail(message);
}
“ condition ”表示要测试的状态,如果“ condition ”的值为 false ,则测试将会失败。
assertNull 与 assertNotNull 断言
assertNull 与 assertNotNull 可以验证所测试的对象是否为空或不为空,如果和预期的相同则测试成功,否则测试失败, assertNull 定义为:
static public void assertNull(String message, Object object ) {
assertTrue(message, object == null );
}
其中, object 就是要测试的对对象,如果 object 为空,该测试成功,否则失败,是不是很简单。
assertSame 与 assertNotSame 断言
assertSame 和 assertEquals 不同, assertSame 测试预期的值和实际的值是否为同一个参数 ( 即判断是否为相同的引用 ) 。 assertNotSame 则测试预期的值和实际的值是不为同一个参数。 assertSame 的定义为:
static public void assertSame(String message, Object expected, Object actual) {
if (expected == actual)
return ;
failNotSame(message, expected, actual);
}
而 assertEquals 则判断两个值是否相等,通过对象的 equals 方法比较,可以相同引用的对象,也可以不同。
fail 断言
“ fail ”断言能使测试立即失败,这种断言通常用于标记某个不应该被到达的分支。例如 assertTrue 断言中, condition 为 false 时就是正常情况下不应该出现的,所以测试将立即失败, fail 的定义为:
static public void fail(String message) {
throw new AssertionFailedError(message);
}
当一个失败或者错误出现的时候,当前测试方法的执行流程将会被中止,但是位于同一个测试类中的其他测试将会继续运行。
JUnit 测试
JUnit 是为 Java 程序开发者实现单元测试的一个框架,它能使得 Java 单元测试更规范有效,并且更有利于测试的集成。
TestCase 测试类
前面已经介绍了 JUnit 中部分断言实现,但是不能够把断言直接写到源代码中,最终运行发布的代码和测试应该是分离的两个部分,源程序是不应该知道有任何的单元测试的存在的。
JUnit 框架中通过 TestCase 实现单元测试, TestCase 继承了 Assert 类,也就是说在 TestCase 类以及子类中可以直接使用 JUnit 框架所提供的断言。另外, TestCase 实现了 Test 接口, Test 接口的 run 方法将会运行一个测试,并返回结果。 TestCase 类的继承关系及方法的实现如图 1 所示:
图 1 TestCase 的类继承关系
如果需要使用 JUnit 做单元测试,可以继承 TestCase ,每一个 TestCase 的子类可以作为独立的测试用例,从而实现测试的目的。
TestSuite 测试组
一个 TestCase 用例会包含多个 test 开头的测试函数,而一个应用中,会包括若干个 TestCase 的用例,通常用户会运行应用中的所有的测试,从而确保应用是可以运行的。有些测试是非常耗费时间,如果每次进行一个小的改动就运行所有的测试,这是很多开发人员不可接受的,这时就需要组合一些 TestCase 用例以及 TestCase 中的方法,把测试分成很多的组,每次只针对特定的组进行测试。
JUnit 框架提供了 TestSuite 套件来组合测试类及测试方法, TestSuite 实现了 addTestSuite 方法和 addTest 方法, addTestSuite 和 addTest 能添加一个测试类或另一个 TestSuite 到当前组中,通过这种组合,用户能够把要测试的用例及方法分成一个组,最后组成一个测试的套件。
Setup 与 Tear-down
通常每个测试的运行都应该是独立的,从而就可以在任何时候,以任意的顺序运行每个单独的测试。然而有些时候需要一些全局的环境变量设置,每个测试用例都可以用到这些设置,而不必每个测试方法运行前都重新设置,这就需要在测试用例运行前使用不同的初始化方式。
测试用例的每一个方法的执行都是相互独立的,每个 test 方法执行之前, JUnit 框架会执行相应的初始化方法,从而保证每个 test 方法的执行和其它 test 方法之间没有关系。当 test 方法执行后, JUnit 框架也会自动执行清理方法。测试方法的初始化和清理工作是通过实现 TestCase 子类的 setup 和 tearDown 方法完成的。
要分清楚 TestCase 和 TestSuite 中方法的执行顺序,初始化条件,这对设计测试项目以及程序的设计都是非常重要的。
使用 Eclipse 进行单元测试
Eclipse 对 JUnit 提供了完美的支持,开发人员可以通过 Eclipse 自动生成单元测试的框架,并且能够运行测试代码。
创建测试用例
在 Eclipse 中,如果要为某一个被测试的类创建一个单元测试类,如例程 1 所示:
例程 1 SimpleAdd.java
可以选择新建向导,打开新建文件对话框,在新建文件对话框中选择 JUnit 下面的“ JUnit Test Case ”选项,打开创建 TestCase 的对话框,如图 1 所示。
图 1 创建 TestCase 对话框
在创建 TestCase 的对话框中,可以选择是否创建 setup 和 tearDown 方法等,另外还能够选择为哪一个类创建对应的单元测试,图 1 选择的被测试类为 SimpleAdd 。选择 Next 按钮,可以为被测试的类选择方法加入到单元测试中,如图 2 所示。
图 2 选择被测试方法对话框
选择 Finish 按钮, Eclipse 将会自动生成选择方法的相关定义,如例程 2 所示。
例程 2 SimpleAddForEclipseTest.java
Eclipse 已经创建 SimpleAddForEclipseTest 类的骨架,当然, Eclipse 没法生成测试方法的实现,这就是开发人员要做的事情了。例如修改 testIntegerAdd 方法如下:
// 测试 SimpleAdd 类的 StringAdd 方法
运行、调试测试用例
通过 Eclipse 可以直接运行单元测试,选择单元测试文件,例如 SimpleAddForEclipseTest 类,修改测试方法后,单击右键菜单,选择 Run as?JUnit Test 菜单,如图 3 所示。
图 3 运行单元测试
运行单元测试后, Eclipse 将会打开 JUnit 的视图,显示当前运行的单元测试是否通过测试,并显示相应的状态,如图 4 所示。
图 4 单元测试的结果
如图所示,如果状态条的颜色为绿色,表示单元测试通过,如果状态条的颜色为红色,表示当前测试未通过, Eclipse 也会同时打印出相应的错误堆栈信息。
如果要跟踪单元测试的代码,可以直接使用 Eclipse 的调试功能,另外, JUnit 的错误堆栈信息也能够定位到相应的出错位置。
提示:可以通过“红灯停,绿灯行”这句话来记住单元测试是否通过。
创建测试组
创建 TestSuite 的方式和创建 TestCase 相似,选择“ JUnit Test Suite ”创建向导,在同一个 TestSuite 中可以选择同一个包下面的多个 TestCase ,如图 5 所示。
图 5 创建 TestSuite
单击 Finish 按钮,可以创建相应的 TestSuite ,如例程 3 所示。
例程 3 AllTests.java
AllTests 为 Eclipse 自动生成的 TestSuite 类, TestSuite 类的运行和调试方式和 TestCase 类相同。
如果通过 Eclipse 创建了 TestCase 或 TestSuite 子类后,程序编译出错,可以手动添加 junit.jar 到项目中,或者通过项目属性添加一个 JUnit 的 Library 。