6.5.1 单元测试
单元测试也称模块测试,其目的是集中检验软件设计的最小单元——模块,检查每个模块是否能独立、正确地运行。在软件系统中。每个模块有独立的功能,各个模块之间相互依赖关系较少,检查模块正确性的测试方案也较容易设计。在这个阶段所发现的错误往往是在编码和详细设计时产生的,通常在编码阶段就应该进行单元测试。
单元测试时,在正式测试之前必须先通过编译程序检查并改正所有语法错误,然后用详细设计描述作指南,对重要的执行通路进行测试,以便发现模块内部的错误。单元测试通常采用白盒测试方法,而且对多个模块的测试可以并行地进行。
(1)单元测试的主要任务
①程序语法检查。通过编译语言对程序进行检查,人工检查。
②程序逻辑检查。检查程序的逻辑结构是否正确,程序中所使用的循环语句的上下项以及循环次数是否有问题,函数或子模块是否有自我调用问题。
③模块接口测试。模块接口测试是单元测试的第一步,前两个任务只是程序走查。模块接口是模块内与模块外联系的关键部位,是保证模块间高内聚低耦合的关键。当模块通过外部调用时,数据必须能够正确流入,当模块结束问题的处理返回调用模块时,数据必须能够正确流出,这样,模块才能完成它的功能。
④局部数据结构测试。局部数据结构测试是为了保证临时存储模块在模块内的数据。模块错误的根源往往是局部数据结构,表现的形式主要有以下几点:不合适或者不相容的类型说明;变量无初值;变量初始化或默认值有错;不正确的变量名或不正确的截断;出现上溢、下溢或地址异常。
⑤路径测试。对模块中的重要执行路径进行测试,路径错误主要是由错误的计算、不正确的比较或不正常的控制流导致。
⑥边界条件测试。边界条件测试是单元测试中最重要的一项任务,边界测试执行的好,可以大大提高程序的质量。边界测试要注意以下容易出现错误的因素,程序内有一个n次循环,这个n次循环应该是1~n,而不是0~n;由<、<=、=、>、>=、≠确定的比较值出错;出现上溢、下溢或地址异常。
⑦错误处理。利用测试,查找错误。
⑧代码书写规范检查。应考虑下列因素:如模块程序设计框架流程图;代码书写规范,对齐方式;代码的注释;参数类型、数据长度、指针、数组长度、大小;输入输出参数结果。
由于模块并不是独立的程序,模块测试时,要增加少量程序段,使模块能够接收数据,或输出一些数据来代替模块之间的接口。在模块连接后再将这些临时增加的程序段去掉。这些临时性程序段可称为测试模块,这需要增加软件成本,但也是必不可少的成本。
(2)单元测试特点(https://www.daowen.com)
①它是一种验证行为。程序中的每一项功能都要用测试来验证它的正确性。单元测试被公认为软件开发过程中的一个关键步骤。单元测试能够简化错误检测。由于能够更容易地找到错误,会减少发现它们的时间和资源。在减少时间和成本的同时,可提高软件质量。
②它是一种设计行为。单元测试会使编程人员从调用者的角度观察和思考问题,为了编写测试用例,迫使编程人员把程序设计成易于调用的和可测试的,即迫使编程人员解除软件中的耦合。
③它是一种编写文档的行为。单元测试是展示函数或类如何使用的最佳文档。该文档是可编译、可运行的,并且应保持最新状态,永远与代码同步。
④它具有回归性。自动化的单元测试避免了代码出现回归,编写完成之后,可以随时随地快速运行测试。
⑤提升反馈速度,减少重复工作,提高开发效率。开发人员实现某个功能或者修补了某个Bug,如果有相应的单元测试支持的话,开发人员可以马上通过运行单元测试来验证之前完成的代码是否正确。
⑥保证最后的代码修改不会破坏之前代码的功能。项目越做越大,代码也越来越多,特别涉及一些公用接口之类的代码或是底层的基础库,谁也不敢保证每次修改的代码不会破坏之前的功能,所以与此相关的需求会被搁置或推迟,由于不敢改进代码,代码也变得越来越难以维护,质量也越来越差,而单元测试恰恰是解决这一问题的好方法。
⑦让代码维护更容易。由于给代码编写很多单元测试,相当于给代码加上了规格说明,开发人员通过阅读单元测试代码也能够帮助其理解现有代码。
⑧有助于改进代码质量和设计。很多易于维护、设计良好的代码都是通过不断的重构才得到的。虽然说单元测试本身不能直接改进代码的质量,但它为代码提供了“安全网”,让开发人员可以大胆地改进代码。
(3)单元测试环境。单元测试常常和代码编写同步进行,在完成了程序编写、复查和语法正确性验证后,就应进行单元测试用例设计。在对每个模块进行单元测试时,不能完全忽视它们和周围模块的相互联系。为模拟这一联系,在进行单元测试时,需设置若干辅助测试模块。辅助模块有两种,一种是驱动模块(Driver),用以模拟被测模块的上级模块。驱动模块在单元测试中接受测试数据,把相关的数据传送给被测模块,启动被测模块,并打印出相应的结果。另一种是被调用模拟子模块(Sub),用以模拟被测模块工作过程中所调用的模块。被调用模拟子模块由被测模块调用,它们一般只进行很少的数据处理,例如,打印入口和返回,以便于检验被测模块与其下级模块的接口,图6.3显示了一般的单元测试环境。

图6.3 一般的单元测试环境
编写驱动器和被调用模拟子模块都是额外的开销,这两种模块虽然在单元测试中必须编写,但却不作为最终的软件产品提供给用户。如果驱动器和被调用模拟子模块很简单,那么开销相对较低,然而,使用“简单”的模块是不可能进行足够的单元测试的,模块间接口的全面检验要推迟到集成测试时进行。