第一章单元测试实施要点单元测试主要从模块的以下5个特征着手进行检查。
1. 模块接口模块的接口保证了测试模块的数据流可以正确地流人、流出。
在测试中应检查以下要点:1) 测试模块的输入参数和形式参数在个数、属性、单位上是否一致。
2) 调用其他模块时所给出的实际参数和被调用模块的形式参数在个数、属性、单位上是否一致。
3) 调用标准函数时所用的参数在属性、数目和顺序上是否正确。
4) 全局变量在各模块中的定义和用法是否一致。
5) 输入是否仅改变了形式参数。
6) 开/关的语句是否正确。
7) 规定的I/O格式是否与输入输出语句一致。
8) 在使用文件之前是否已经打开文件或是使用文件之后是否已经关闭文件。
2. 局部数据结构。
在单元测试中,局部数据结构出错是比较常见的错误,在测试刚应重点考虑以下因素:1) 变量的说明是否合适。
2) 是否使用了尚未赋值或尚未初始化的变量。
3) 变量的初始值或默认值是否正确。
4) 变量名是否有错(例如拼写错)。
3. 重要的执行路径。
在单元测试中,对路径的测试是最基本的任务。
由于不能进行穷举测试,需要精心设计测试用例来发现是否有计算、比较或控制流等方面的错误。
1) 计算方面的错误:算术运算的优先次序不正确或理解错误;精度不够;运算对象的类型不匹配;算法错;表达式的符号表示不正确等。
2) 比较和控制流的错误:本应相等的量由于精度造成不相等;不同类型进行比较逻辑运算符不正确或优先次序错误;循环终止不正确(如多循环一次或少循环一次)、死循环;不恰当地修改循环变量;当遇到分支循环时,出口错误等。
4. 出错处理。
好的设计应该能预测到出错的条件并且有出错处理的途径。
虽然计算机机可以显示出错信息的内容,但仍需要程序员对出错进行处理,保证其逻辑的正确性以便于用户维护。
5. 边界条件边界条件的测试是单元测试的最后工作,也是非常重要的工作。
毫件容易在边界出现错误。
块进行测试时,需要开发两种模块:6. 驱动模块相当于一个主程序,接收测试用例的数据,将这些数据送到测试椁,输出测试结果。
7. 桩模块也称为存根模块。
桩模块用来代替测试模块中所调用的子模块,其进行少量的数据处理,目的是为了检验人口,输出调用和返回的信息。
提高模块的内聚度可以简化单元测试。
如果每个模块只完成一种功能,对于具一块来讲,所需的测试方案数据就会显著减少,而且更容易发现和预测模块中的错误。
第二章单元测试经验总结1. 概述工厂在组装一台电视机之前,会对每个元件都进行测试,这,就是单元测试。
其实我们每天都在做单元测试。
你写了一个函数,除了极简单的外,总是要执行一下,看看功能是否正常,有时还要想办法输出些数据,如弹出信息窗口什么的,这,也是单元测试,我们把这种单元测试称为临时单元测试。
只进行了临时单元测试的软件,针对代码的测试很不完整,代码覆盖率要超过70%都很困难,未覆盖的代码可能遗留大量的细小的错误,这些错误还会互相影响,当BUG暴露出来的时候难于调试,大幅度提高后期测试和维护成本,也降低了开发商的竞争力。
可以说,进行充分的单元测试,是提高软件质量,降低开发成本的必由之路。
对于程序员来说,如果养成了对自己写的代码进行单元测试的习惯,不但可以写出高质量的代码,而且还能提高编程水平。
要进行充分的单元测试,应专门编写测试代码,并与产品代码隔离。
我们认为,比较简单的办法是为产品工程建立对应的测试工程,为每个类建立对应的测试类,为每个函数(很简单的除外)建立测试函数。
首先就几个概念谈谈我们的看法。
一般认为,在结构化程序时代,单元测试所说的单元是指函数,在当今的面向对象时代,单元测试所说的单元是指类。
以我们的实践来看,以类作为测试单位,复杂度高,可操作性较差,因此仍然主张以函数作为单元测试的测试单位,但可以用一个测试类来组织某个类的所有测试函数。
单元测试不应过分强调面向对象,因为局部代码依然是结构化的。
单元测试的工作量较大,简单实用高效才是硬道理。
有一种看法是,只测试类的接口(公有函数),不测试其他函数,从面向对象角度来看,确实有其道理,但是,测试的目的是找错并最终排错,因此,只要是包含错误的可能性较大的函数都要测试,跟函数是否私有没有关系。
对于C++来说,可以用一种简单的方法区隔需测试的函数:简单的函数如数据读写函数的实现在头文件中编写(inline函数),所有在源文件编写实现的函数都要进行测试(构造函数和析构函数除外)。
2.什么时间开始测试什么时候测试?单元测试越早越好,早到什么程度?XP开发理论讲究TDD,即测试驱动开发,先编写测试代码,再进行开发。
在实际的工作中,可以不必过分强调先什么后什么,重要的是高效和感觉舒适。
从我们的经验来看,先编写产品函数的框架,然后编写测试函数,针对产品函数的功能编写测试用例,然后编写产品函数的代码,每写一个功能点都运行测试,随时补充测试用例。
所谓先编写产品函数的框架,是指先编写函数空的实现,有返回值的随便返回一个值,编译通过后再编写测试代码,这时,函数名、参数表、返回类型都应该确定下来了,所编写的测试代码以后需修改的可能性比较小。
3.谁来测试由谁测试?单元测试与其他测试不同,单元测试可看作是编码工作的一部分,应该由程序员完成,也就是说,经过了单元测试的代码才是已完成的代码,提交产品代码时也要同时提交测试代码。
测试部门可以作一定程度的审核。
4.关于桩代码我们认为,单元测试应避免编写桩代码。
桩代码就是用来代替某些代码的代码,例如,产品函数或测试函数调用了一个未编写的函数,可以编写桩函数来代替该被调用的函数,桩代码也用于实现测试隔离。
采用由底向上的方式进行开发,底层的代码先开发并先测试,可以避免编写桩代码,这样做的好处有:减少了工作量;测试上层函数时,也是对下层函数的间接测试;当下层函数修改时,通过回归测试可以确认修改是否导致上层函数产生错误。
第三章单元测试的基本策略1.概述当设计一个单元测试的策略时,可以采用三种基本的组织方法。
它们分别是自上而下法、自下而上法和分离法。
在接下来的第二、第三和第四部分将对上述三种方法的详细内容、各自的优点和缺点分别进行介绍。
在文章中要一直用到测试驱动和桩模块这两个概念。
所谓的测试驱动是指能使软件执行的软件,它的目的就是为了测试软件,提供一个能设置输入参数的框架,并执行这个框架单元以得到相应的输出参数。
而桩模块是指一个模拟单元,用这个模拟单元来替代真实的单元完成测试。
2. 自上而下法2.1 详述在自上而下的测试过程中,每个单元是通过使用它们来进行测试的,这个过程是由调用这些被测单元的其他独立的单元完成的。
首先测试最高层的单元,将所有的调用单元用桩模块替换。
接着用实际的调用单元替换桩模块,而继续将较低层次的单元用桩模块替换。
重复这个过程直到测试了最底层的单元。
自上而下测试法需要测试桩,而不需要测试驱动。
图2.1描述了使用测试桩和一些已测试单元来测试单元D的过程,假设单元A,B,C 已经用自上而下法进行了测试。
由图2.1得到的是一个使用基于自上而下组织方法的单元测试计划,其过程可以描述如下:1)步骤1:测试A单元,使用B,C,D单元的桩模块。
2)步骤2:测试B单元,通过已测试过的A单元来调用它,并且使用C,D单元的桩模块。
步骤3:测试C单元,通过已测试过的A单元来调用它,并且使用已通过测试的B单元和D单元的桩模块。
3)步骤4:测试D单元,从已测试过的A单元调用它,使用已测试过的B和C单元,并且将E,F和G单元用桩模块代替。
(如图2.1所示)4)步骤5:测试E单元,通过已测试过的D单元调用它,而D单元是由已通过测试的A单元来调用的,使用已通过测试的B和C单元,并且将F,G,H,I和J单元用桩模块代替。
5)步骤6:测试F单元,通过已测试过的D单元调用它,而D单元是由已通过测试的A单元来调用的,使用已通过测试的B,C和E单元,并且将G,H,I和J单元用桩模块代替。
6)步骤7:测试G单元,通过已测试过的D单元调用它,而D单元是由已通过测试的A单元来调用的,使用已通过测试的B,C和F单元,并且将H,I和J单元用桩模块代替。
7)步骤8:测试H单元,通过已测试过的E单元调用它,而E单元是由已通过测试的D单元来调用的,而D单元是由已通过测试的A单元来调用的,使用已通过测试的B,C,E,F,G和H单元,并且将J单元用桩模块代替。
8)步骤9:测试J单元,通过已测试过的E单元调用它,而E单元是由已通过测试的D单元来调用的,而D单元是由已通过测试的A单元来调用的,使用已通过测试的B,C,E,F,G,H和I单元2.2 优点自上而下单元测试法提供了一种软件集成阶段之前的较早的单元集成方法。
实际上,自上而下单元测试法确实将单元测试和软件集成策略进行了组合。
单元的详细设计是自上而下的,自上而下的测试实现过程使得被测单元按照原设计的顺序进行,因为单元测试的详细设计与软件生命周期代码设计阶段的重叠,所以开发时间将被缩短。
在通常的结构化设计中,高等级的单元提供高层的功能,而低等级的单元实现细节,自上而下的单元测试将提供一种早期的“可见”的功能化集成。
它给予单元测试一种必要的合理的实现途径。
较低层次的多余功能可以通过自上而下法来鉴别,这是因为没有路径来测试它。
(但是,这可能在区分多余的功能和没有被测试的功能时带来困难)。
2.3 缺点自上而下法是通过桩模块来进行控制的,而且测试用例常常涉及很多的桩模块。
对于每个已测单元来说,测试变得越来越复杂,结果是开发和维护的费用也越来越昂贵。
依层次进行的自上而下的测试,要达到一个好的覆盖结构也很困难,而这对于一个较为完善、安全的关键性应用来说至为重要,同时这也是很多的标准所要求的。
难于达到一个好的覆盖结构也可能导致最终的多余功能和未测试功能之间的混乱。
由此,测试一些低层次的功能,特别是错误处理代码,将彻底不切实。
一个单元的变化往往会影响对其兄弟单元和下层单元的测试。
例如,考虑一下D单元一个变化。
很明显,对D单元的单元测试不得不发生变化和重新进行。
另外,要使用已测试单元D的E、F、G、H、I和J单元也不得不重新测试。
作为单元D改变的结果,上述测试自身可能也不得不发生改变,即使单元E、F、G、H、I和J实际上并没有改变。
这将导致当变化发生时,重复测试带来的高成本,以及高额的维护成本和高额的整个软件生产周期的成本。
在为自上而下测试法设计测试用例当中,当被测单元调用其他单元时需要测试人员具备结构化知识。
被测试单元的顺序受限于单元的层次结构,低层次的单元必须要等到高层次的单元被测试后才能被测试,这样就形成了一个“又长又瘦”的单元测试阶段。
(然而,这可能会导致测试详细设计与软件生命周期编码阶段的整体重叠。
)如图2.1所示的例子程序中各个单元之间的层次关系十分简单,在实际的编程过程中可能会遇到类似的情形,而且各个单元之间的层次关系会更复杂。