自动化测试空间: http://www.automationqa.com/ 性能测试讨论群:119821036

单元测试的基本测试方法

上一篇 / 下一篇  2011-09-23 10:06:28

1、单元测试的基本方法

  单元测试的基本方法有:人工静态分析、自动静态分析、自动动态测试,人工动态测试。

  人工静态分析:通过人工阅读代码来查找错误,一般是程序员交叉查看对方的代码,可能发现有特征错误和无特征错误。

  自动静态分析:使用工具扫描代码,根据某些预先设定的错误特征,发现并报告代码中的可能错误,自动静态分析只能发现语法特征错误。

  自动动态测试:使用工具自动生成测试用例并执行被测试程序,通过捕捉某些行为特征(如产生异常/程序崩溃等)来发现并报告错误,自动动态测试只能发现行为特征错误,对无特征错误完全无能为力,例如,前面所说的加法函数,代码可以说是最简单的,错误也是最简单的,但是自动动态测试仍然无法发现,因为测试工具不可能自动了解代码的功能

  人工动态测试:人工设定程序的输入和预期的正确输出,执行程序,并判断实际输出是否符合预期,如果不符合预期,自动报告错误。这里所说的"人工",仅指测试用例的输入和预期输出是人工设定的,其他工作可以由人工完成,也可以借助工具自动完成。人工动态测试可以发现有特征错误和无特征错误,例如,前面所说的加法函数,只要人工建立一个测试用例,输入两个1,并判断输出是否等于2,运行测试,就可以发现代码中含有错误。

  以上四种方法还可以进一步细分,例如,人工动态测试又有多种设计测试用例的方法,如果根据程序的功能来设计测试用例,就是黑盒测试,如果根据代码及代码的逻辑结构来设计测试用例,就是白盒测试

  2、测试方法的选择

  工作中是不是把各种测试方法不分轻重都做一遍呢?显然不行,项目工期和预算不会允许这么做,也不符合效益原则,应该选择一种方法作为主要测试方法,其他视情况取舍。

  自动静态分析、自动动态测试只能发现有特征错误,这两种方法加起来,做到最好也仅限于发现有特征错误,而多数语法特征错误编译器就能发现,很多行为特征错误会在开发过 程中,或集成测试和系统测试中自动暴露出来,所以这两种方法不宜作为主要测试方法。

  人工静态分析虽然可能发现有特征错误和无特征错误,但是要彻底找出所有错误来,显然太难了。

  人工动态测试可以发现有特征错误和无特征错误,并且具有广阔的发挥空间,可以作为主要测试方法。

  3、黑盒测试与白盒测试

  常常见到"单元测试是白盒测试","单元测试也有黑盒"之类的说法,容易引起混乱。黑盒与白盒其实是测试方法,黑盒就是针对系统的外部特性进行测试,把目标系统看作一个黑盒子,不考虑内部结构;白盒就是针对系统的内部结构进行测试。各个测试阶段都可以使用黑盒方法和白盒方法,即无论是单元测试、集成测试、系统测试阶段都可以使用黑盒方法和白盒方法。

  黑盒测试又叫功能测试,我们首先要测试程序是否实现了基本功能,因此,黑盒测试是基本测试。黑盒测试的主要缺陷是难于衡量完整性,而白盒测试正好可以弥补个缺陷。

  白盒测试通过逻辑覆盖率来衡量完整性,具有可以精确统计的数字指标。逻辑单位主要有:语句、分支、条件、条件值、条件值组合,路径。语句覆盖就是覆盖所有的语句,其他类推。另外还有一种判定条件覆盖,其实是分支覆盖与条件覆盖的组合。跟条件有关的覆盖就有三种,解释一下:条件覆盖是指覆盖所有的条件表达式,即所有的条件表达式都至少计算一次,不考虑计算结果;条件值覆盖是指覆盖条件的所有可能取值,即每个条件的取真值和取假值都要至少计算一次;条件值组合覆盖是指覆盖所有条件取值的所有可能组合。与条件直接有关的错误主要是逻辑操作符错误,例如:||写成&&,漏了写!什么的,采用分支覆盖与条件覆盖的组合,基本上可以发现这些错误,而条件值覆盖与条件值组合覆盖往往需要大量的测试用例,因此,条件值覆盖和条件值组合覆盖的效费比偏低,比较有价值的覆盖率是语句覆盖、条件覆盖、分支覆盖、路径覆盖。

  4、测试用例

  人工动态测试需要人工设计测试用例。一个测试用例,就是设定输入数据,执行被测试程序,并判断输出是否符合预期。输出符合预期,则测试通过,否则测试失败。一般来说,测试工具要能自动报告失败的测试。

  测试用例的主要内容是输入数据和预期输出,简称输入输出,其中输入是核心,输入确定了,再根据程序的功能设定预期的正确输出。

  如果我们把函数看作测试单元,那么,输入数据就是被测试函数所读取的外部数据及这些数据的初始值。"外部数据"是对于被测试函数来说的,就是除了局部变量以外的其他数据,分为几类:参数、成员变量、全局变量、IO媒体。IO媒体是指文件、数据库或其他储存或传输数据的媒体,例如,被测试函数要从文件或数据库读取数据,那么,文件或数据库中的原始数据也属于输入数据。

预期输出是指:返回值及被测试函数所写入的外部数据的结果值。返回值就不用说了,被测试函数进行了写操作的参数(输出参数)、成员变量、全局变量、IO媒体,它们的预期的结果值都是预期输出。

  一个函数无论多复杂,都无非是对这几类数据的读取、计算和写入,我们的测试目的,就是要检验程序的这些行为是否符合要求。

  总而言之,输入就是被测试程序要读取的数据,我们要设定它们的初始值,输出就是被测试程序要改写的数据,我们要判断它们的结果值是否符合预期。

  我们只需要关注外部数据中真正需读取或写入的部分,不必理会其他部分,例如,一个参数,它是一个结构,有十个域,但是被测试函数只需要读取其中的一个域,那么测试用例只需要为这个域设定初始值;再如,被测试类中有十个成员变量,但是被测试函数只需要读取其中的一个,那么测试只需要为这个成员变量设定初始值。对预期输出的判断也一样。

  5、测试代码

  为了简单和实用,建议这样组织测试代码:一个产品类对应一个测试类,一个产品函数对应一个测试函数,一个测试函数内包括若干个测试用例。

  下面是函数CMyClass::Add(int a, int b){return a+b;};的测试函数:


void CMyClassTester::Add_int_int()
{
//第一个测试用例
{ CMyClass* pObj = new CMyClass(); //1
int i = 0; //2
int j = 0; //3
int ret = pObj->Add(i, j); //4
ASSERT(ret == 0); //5
delete pObj; } //6
}


  把参数表作为测试函数的函数名的一部分,这样当出现重载的被测试函数时,测试函数不会产生命名冲突。第1和第6行建立和销毁被测试对象,所加的{}是为了让每个测试用例的代码有一个独立的域,以便多个测试用例使用相同的变量名。第2和第3行是定义输入数据,第4行是调用被测试函数,第5行是预期输出,它的特点是当实际输出与预期输出不同时自动报错,ASSERT是VC的断言宏,也可以使用其他类似功能的宏,使用测试工具进行单元测试时,可以使用该工具定义的断言宏。更多的测试用例可以通过拷贝现有用例并修改输入输出的方法建立。

  前面的例子是使用产品代码所用的语言来编写测试代码,还有一种方式是使用脚本语言来编写测试代码,后者的优点是无需编译即可执行测试,但缺点也非常明显:需要另外掌握一种脚本语言;复杂数据类型处理起来比较麻烦;前置操作和后置操作很难处理(要使用一种语言去调用另一种语言的代码);在两种语言间来回切换会严重干扰人的思维,边开发边测试调试时,这种干扰思维所损失的时间可能比免编译所节约的时间多得多。

  至于测试数据的组织,前面的例子是直接保存于代码中,另一种方式是保存于电子表格中(包括保存于数据库中),后一种方式对于测试工具开发商来说无疑是一种好选择,因为数据是独立的,测试工具容易保证新旧版本的兼容性,降低了工具开发难度,但却难于适应复杂的应用。测试这样的代码:int Add(int a, int b){return a+b;};数据怎么组织都行,但实际应用却是很复杂的,例如,输入数据是复杂类型,可能只需要为其中的一个域设定初始值,设定初始值的途径又可能是调用对象的接口;输入数据可能需要经过中间处理;需要执行前置操作;输出要判断多个数据的结果值,这些数据又可能是高级类型,需要调用其接口来判断状态;需要执行后置操作等等,这些输入输出实际上就是代码,写在表格中显然极不方便,勉强写在表格中,使用时不可避免会产生大量的编译或解析错误,要反复地修改,反复导入,反复编译或解析,效率低下,难于适应实际的应用。

  6、代码的可测性

  具有良好整体结构的代码,应该符合"低耦合"的特性,形象地说,就是"各家自扫门前雪、不管他人瓦上霜",每一个函数、每一个类、每一个模块,都应该只做自己该做的事,不要把应该由"其他人"做的事扯进来。 通常,"低耦合"的代码具有可测性,具有不当高耦合特性的代码通常不具可测性,一般来说,如果代码不具可测性,那么加入测试工程后,会产生编译错误,导致测试无法进行。

  高质量代码应该具有正确性、可测性、可复用性、可扩展性。可测性是最基本特性,能测试才能检验和提高正确性,具备了可测性,也就具备了最基本的可复用性和可扩展性。解决不当耦合的 较好手段是重构,即改良代码结构,这样不但能顺利完成测试,更重要的保证了代码具有良好的整体结构,使代码易于维护和复用。


TAG:

 

评分:0

我来说两句

Open Toolbar