单元测试是针对代码单元的独立测试。“独立”是指将代码从原始项目及其依赖的环境中隔离出来,针对各个单元单独进行测试,包括三方面含义:形式独立、实质独立和意识独立。单元测试之所以困难,就在于技术上无法解决形式独立和实质独立,主观上没有达到意识独立。
形式独立
将测试任务从原始项目及其依赖环境中隔离出来,并能在易于测试的环境下运行。企业项目通常高耦合、可测性差、依赖于特定的软件环境或硬件环境,单元测试要首先将测试任务从依赖的代码和环境中分离出来,并解决编译、平台差异等问题,使测试任务在易于测试的环境下能够独立测试。
实质独立
被测试代码通常会调用各种底层函数,其功能逻辑难免受底层函数的影响,如何独立完整地进行测试?实际上,调用底层函数获得的数据,与通过参数传递的数据具有同等意义,例如,调用底层函数得到一个返回值,要测试的是被测程序对这个值的各种可能有没有做合适的判断处理,并不关心底层函数如何计算这个值,正如只关心是否对参数的各种可能做了合适处理,而不关心参数是如何传递和由谁传递一样。因此,调用底层函数获得的数据可视为被测函数的一种输入,称为内部输入。
如果可以模拟底层函数的各种输出,并将这些输出视为被测函数的内部输入,那么,无论底层函数属于哪种情形,都可以完整测试被测代码的功能逻辑,这就是实质独立。
内部输入分为易于自然取得(调用真实的底层函数获得)、不可控(调用真实底层函数但其行为难于控制)、失真(打桩的必然后果)、难于自然取得等四种情形。易以自然取得的内部输入并不影响被测函数的独立性,而后三种则是单元测试的关键难点。静态局部变量也是麻烦的内部输入。关于内部输入的详细说明,请看这篇文章。
意识独立
意识独立是主观要素,是指在制定测试目标和执行测试时,要适应“单元测试是针对代码单元的独立测试”这一基本现实,把测试任务看作一个一个的独立单元,正确设定测试目标、正确选择测试方法及工具。
单元测试有独特的优势,例如易于完整地测试代码单元的功能逻辑,这些优势来自“独立”,但凡事有其长必有其短,既然是“独立”,就难于测试与其他代码和依赖环境的相互关系。单元测试与系统测试是互补关系,不是代替关系,“独立”状态下易于发现的错误,才是单元测试的目标;集成后才易于发现的问题,应该留待系统测试。
代码单元本身的功能逻辑错误都是单元测试的目标,而性能问题(时间性能如执行速度,空间性能如存储空间大小、内存泄漏)难于在最小单元内测试,不是单元测试目标。
单元测试也不考虑平行和向上依赖关系,并把向下依赖抽象为内部输入,例如,测试一个函数时,不考虑这个函数在项目中是被普通调用、系统回调、消息或信号调用、还是中断调用;不考虑多线程、重载;嵌入式环境中的端口、寄存器等在代码中都是内存,视为全局变量;并把底层函数(包括操作系统API)的输出视为内部输入。总之,只用各种输入是否产生了正确输出的方式,完整测试函数的功能逻辑,也正因为这个原因,单元测试可以脱离原来的平台环境,在易于实施的平台上进行,例如,在PC上测试嵌入式项目,在Windows上测试Linux项目,都是可行的。
编码规范检查与单元测试无关,无论是否实施单元测试,编码规范检查都是必不可少的工作。静态分析属于全局扫描,严格来说也不是单元测试,提高编译器的警告级别,就是最简单高效的静态分析。
单元测试是意义重大且困难的工作,目标应该具体而明确,将不属于单元测试或单元测试不擅长的目标牵扯进来,其结果往往是“拣了芝麻,丢了西瓜”。
|