【IT168 技术文章】
最近有人对我提到的单元测试的概念产生了疑问,原因是他看到了《软件测试方法和技术》(朱少民 清华大学出版社 2005年7月第一版)中的一段对单元测试的定义,对比我在单元测试中提到的主要是针对类的单元测试,觉得两者不太一致,于是对单元测试的对象和范围产生了一些疑问。
必须说明的是,对这本《软件测试方法和技术》,我个人的评价应该是不错的,在体系结构和组织上都比较清晰,包含的内容也比较完整。缺点可能是过于中规中举了(个人看法:)),和国内的其他教科书一样,没有多少引申和深入的描述。因此,本文仅用来阐述个人对单元测试的观点,不涉及任何对该书或是作者的评价。
《软件测试方法和技术》中对单元测试的描述原文如下(P86):
“单元测试是对软件基本组成单元的测试。单元测试的对象是软件设计的最小单位——模块。很多参考书中将单元测试的概念误导为一个具体函数或一个类的方法。一个最小的单元应该有明确的功能、性能定义、接口定义而且可以清晰地与其他单元区分开来。一个菜单、一个显示界面或者能够独立完成的具体功能都可以是一个单元。某种意义上单元的概念已经扩展为组件(component)。”
其实这里的观点我大部分都同意,就是对“很多参考书中将单元测试的概念误导为一个具体函数或一个类的方法”的说法有些存疑。毫无疑问,作者的观点是,单元测试不应该是针对具体函数和类的方法的测试,接下来作者说明了他对单元的理解:“一个最小的单元应该有明确的功能、性能定义、接口定义而且可以清晰地与其他单元区分开来”,对这个单元的理解我完全同意,但困扰我的就是,我并没有从这个定义中看出来一个具体函数不能作为单元测试中“单元”的理由。这段文字中提到单元是“具有明确的功能定义、性能定义、接口定义”,那我们来看看一个具体的函数,例如,C语言中的printf函数,一样可以套用这样的定义:
printf函数
功能定义:将输入参数按照制定的格式打印输出
性能定义:(略,不同平台上应该有不同的性能标准)
接口定义:可用函数的参数作为函数对外的接口
另外,这段文字中提到的“一个菜单、一个显示界面或者能够独立完成的具体功能都可以是一个单元”,在我看来仍然有些含糊——具体什么才是“能够独立完成的具体功能”,我想这里的“功能”不可能对应到具体的用户需求中的功能,而是指设计中体现的功能,如果是这个概念,那么一个函数毫无疑问是具有功能的。
为了慎重,我特地查阅了一些经典的软件测试书和定义中的单元测试内容,以下是引用和我的理解。
swebok 2004应该算得上是软件工程领域的权威性文档了,其中对unit testing的定义如下:
“Unit testing verifies the functioning in isolation of software pieces which are separately testable. Depending on the context, these could be the individual subprograms or a larger component made of tightly related units.”
从这里可以看到,swebok 2004对unit的定义具有这些特性:一个单元是一个“software pieces”,具有“separately testable”的特性,从这个角度来说,并没有任何理由支持一个函数不能是单元测试对象的论据。而且,swebok 2004还明确说明了,在不同的上下文中,unit可以是子程序,也可以是紧密联系的一些单元组成的component。
《统一软件开发过程》中也对单元测试进行了描述(P223):
“执行单元测试的目的是为了把已实现的构件作为个体单元进行测试,主要有以下几类单元测试:
规格说明测试(specification testing)或“黑盒测试”,验证单元外观上的可观察的行为;
结构测试(structure testing)或“白盒测试”,验证单元的内部实现”
这里的关键问题在于上文中“构件”的概念,构件是RUP实现过程中的产物,在该书的P207中给出了构件的定义:“构件是模型元素(如设计模型中的设计类)的物理包”,可见,根据这样的描述,将一个类或是一段代码片断作为单元测试的目标绝对是RUP所认可的。
接下来是《Testing Objected-Oriented Systems, Models, Patterns, and tools》(《面向对象系统测试 模型,视图与工具》)这本大部头的书,我手中的是清华大学出版社出版的影印本,其中没有直接的对单元测试的定义,但在描述class scope testing时,有如下描述(P350):
“Class scope testing corresponds with the classical definition of unit testing: exercising a relatively small software component, usually via a driver, in isolation.”
我认为这段话也清楚地表明了作者认为针对类的测试是属于单元测试的立场。
当然,还有一些书中的对单元测试的描述我也进行了引用,例如,在《The art of Software Testing (2nd)》一书中,单元测试被描述为:
“Module testing (or unit testing) is a process of testing the individual subprograms, subroutines, or procedures in a program. That is, rather than initially testing the program as a whole, testing is first focused on the smaller building blocks of the program.”
在《Pragmatic Unit Testing in C Sharp with NUnit》书中,单元测试描述如下:
“A unit test is a piece of code written by a developer that exercises a very small, specific area of functionality of the code being tested. Usually a unit test exercises some particular method in a particular context. For example, you might add a large value to a sorted list, then confirm that this value appears at the end of the list. Or you might delete a pattern of characters from a string and then confirm that they are gone.
Unit tests are performed to prove that a piece of code does what the developer thinks it should do.”
总之,从以上的内容来看,似乎没有任何一本书直接支持《软件测试方法和技术》中提到的这个观点。
对于单元测试,我的观点是:
单元测试是针对“单元”的测试,这是毫无疑问的。但何谓“单元”,则并没有特别明确的区分,在不同的上下文语境中,单元可以是不同的含义。在RUP中,单元测试的对象可以是文件、DLL或是类等等。
从《Systematic Software Testing》提供的单元测试模板来看,在确定单元测试范围的时候,单元测试范围的内容可以是“Unit/Module/Component”,可见,在作者看来, 单元测试的范围可以是Unit(代码片断)、Module(模组)或者是Component(构件)。
从目前的TDD和单元测试的发展趋势来看,在使用面向对象方法开发的系统中,越来越多的人倾向于将单元测试理解为针对类的测试,或是针对几个紧密联系的类的测试,并不需要被测单元具有UI层面上的含义(例如,一个纯粹的算法类)。
在实际的工作中,具体“单元”的选取应该是根据具体的情况来确定的,没有一个非常统一的标准。例如,如果该系统大量使用外部的组件(component),单元测试的内容更多的是以组件来进行,但如果系统开发中要求对每个类进行测试,类就应该是单元测试的最好关注单位。