前言
考察目前关于单元测试和JUnit的文章,要么是介绍单元测试的理论,要么是通过一个简单的HelloWorld例子介绍工具的使用。这样很容易使读者在实际应用中无从下手。因为只有工具而没有理论的指导,将严重消弱了工具的作用,终只能是沙滩建楼,达不到预期的目标;只有理论而没有工具的支持,也使得理论难有很好的着力点,终使理论流于空泛。本文试图通过先讲解单元测试理论,进而将这些理论结合到JUnit的使用当中,后通过对一个实用的、可以重用的时间操作类采用JUnit进行单元测试来完整阐述单元测试的思想、方法、以及工具的使用。作者相信,只有通过这样,才能让读者真正把单元测试做好。
1. 为什么要进行单元测试
一个特定的开发组织或软件应用系统的测试水平取决于对那些未发现的Bug的潜在后果的重视程度。这种后果一方面常常会被软件的开发人员所忽视,而另一方面却有可能损害组织的信誉,并且会导致对未来的市场产生负面的影响。相反地,一个可靠的软件系统的良好的声誉将有助于一个开发组织获取未来的市场。
很多研究成果表明,无论什么时候作出修改都要进行完整的回归测试,在生命周期中尽早地对软件产品进行测试将使效率和质量得到好的保证。Bug发现得越晚,修改它所需的费用越高,因此从经济角度来看,应该尽可能早的查找和修改Bug。在修改费用变得过高之前,单元测试是一个在早期抓住Bug的机会。
相比后阶段的测试,单元测试的创建更简单、维护更容易,并且可以更方便的进行重复。 从全程的费用来考虑,相比起那些复杂且旷日持久的集成测试,或是不稳定的软件系统来说, 单元测试所需的费用是很低的。研究显示高达50%的维护工作量被花在那些总是会有的Bug的修改上面。如果这些Bug在开发阶段被排除掉的话,那么工作量可以节省下来。当考虑到软件维护费用可能会比初的开发费用高出数倍的时候,这种潜在的对50%软件维护费用的节省将对整个软件生命周期费用产生重大的影响。
2. 什么是单元测试
单元测试是对小的可测试软件元素(单元)实施的测试,它所测试的内容包括内部结构(如逻辑和数据流)以及单元的功能和可观测的行为。这里的单元不一定是指一个具体的函数或一个类的方法,“单元”是:
(1)可测试的、小的、不可再分的程序模块。
(2)有明确的功能、规格定义。
(3)有明确的接口定义,清晰地与同一程序的其他单元划分开来。
在具体实现时,单元测试也可能对应的是多个程序文件中的一组函数。在一种传统的结构化编程语言中,比如C,要进行测试的单元一般是函数或子过程。在象C++这样的面向对象的语言中,要进行测试的基本单元是类。单元测试的原则同样被扩展到第四代语言(4GL)的开发中,在这里基本单元被典型地划分为一个菜单或显示界面。
3. 单元测试的一般方法
单元测试的方法一般分为两类:白盒方法和黑盒方法。白盒方法通常是分析单元内部结构后通过对单元输入输出的用例构造,达到单元内程序路径的大覆盖,尽量保证单元内部程序运行路径处理正确,它侧重于单元内部结构的测试,依赖于对单元实施情况的了解。
黑盒方法通过对单元输入输出的用例构造验证单元的特性和行为,侧重于核实单元的可观测行为和功能,并不依赖于对单元实施情况的了解。进行单元测试必须综合使用上述两个方法,否则,单元测试很可能是不成功、不完整和不彻底的。
4. 单元测试的目标
单元测试要达到的目标,总体来说是保证单元内部的处理是正确的、没有遗漏和多余功能。细分而言,单元测试要达到以下几个目标:
(1)信息能否正确地流入和流出单元。
(2)在单元工作过程中,其内部数据能否保持其完整性,包括内部数据的形式、内容及相互关系不发生错误,也包括全局变量在单元中的处理和影响。
(3)在为限制数据加工而设置的边界处,能否正确工作。
(4)单元的运行能否做到满足特定的逻辑覆盖。
(5)单元中发生了错误,其中的出错处理措施是否有效。
5. 为什么要使用JUnit进行单元测试
5.1. 什么是JUnit
JUnit是对程序代码进行单元测试的一种Java框架。通过每次修改程序之后测试代码,程序员可以保证代码的的少量变动不会破坏整个系统。官方对JUnit的定义是“JUnit is a simple framework to write repeatable tests.”。
5.2. 自己编写测试框架的弊病
自己编写测试框架进行单元测试一般有两个方法。第一种方法是在要测试的类的main()方法中编写测试代码。随着程序越变越大,这种开发方法很快开始显现出了缺陷:
(1)混乱。类接口越大,main() 越大。类可能仅仅因为正常的测试变得非常庞大。
(2)代码膨胀。由于加入了测试,所以产品代码比所需要的要大。
(3)测试不可靠。main() 是代码的一部分,main() 对其他开发者通过类接口无法访问的私有成员和方法享有访问权。出于这个原因,这种测试方法很容易出错。
(4)很难自动测试。要进行自动测试,必须创建另一程序来将参数传递给 main()。第二种方法是编写一个测试类框架,它虽然能够克服上个方法的缺陷,但增加了开发组织维护这个测试类框架的工作量,为立即大规模的重用设置障碍。而且,由于这个测试框架是内部开发的,存在着与业界难于交流和沟通的弊病。
5.3. JUnit的优势
(1)需要编写自己的框架。
(2)它是开放源代码,因此不需要购买框架。
(3)开放源代码社区中的其他开发者会使用它,因此可以找到许多示例。
(4)可以将测试代码与产品代码分开。
(5)易于集成到构建过程中。