单元测试目的和意义
对于非正式的软件(其特点是功能比较少,后续也不有新特性加入,不用负责维护),我们可以使用debug单步执行,内存修改,检查对应的观测点是否符合要求来进行单元测试,这种方法速度比较快,使用也比较方便,如果使用单元测试框架编写单元测试用例测试,则效率很低。
但是对于正式商用的软件,往往拥有大量的特性,采用敏捷进行开发,会经过多次迭代,每次迭代都会有新特性加入。而且发布之后也会经常收到用户需求,进行特性的增量开发。如何能保证新加入的特性以及问题修改不会影响到已有的特性。这时编写单元测试用例就比较有作用了。
首先单元测试可以保证代码质量。通过单元测试不但可以对函数正常功能进行测试,而且可以将一些系统测试时很难构造的异常场景都覆盖到。有效的保证代码质量。
其次保证代码的可维护以及可扩展。每次特性变化都编写单元测试用例,这样单元测试用例会覆盖所有的特性,这样的话后续对于代码重构优化、以及新特性代码的加入都可以观察到对老的特性影响。
测试驱动开发(TDD),在正式代码编写前,可以先考虑进行单元测试代码的编写,这样可以保证功能的完备性,并较好的优化代码结构。
单元测试思路和方法
单元测试开始的时间
软件开发过程中,单元测试和编码共属实现阶段,进行动态的单元测试前要先对程序进行静态分析(例如PCLint等)和代码审查。
因为使用动态测试技术要准备测试用例,进行结果记录和分析,工作量大,发现错误太多会降低动态测试效率;所以先使用静态分析和代码审查技术,能充分地发挥人的判断和思维优势,检查出对机器而言很难发现的错误。典型的包括代码和设计规格的一致性,代码逻辑表达式的正确性。一旦发现错误,就知道错误的性质和位置,调试代价较低;
单元测试代码的结构
单元测试一般是对函数级别的测试,测试目的函数的输入输出,检查在特定的输入情况下,对应的输出是否符合期望值。其输入可能是函数的参数,也可能是函数中从其他模块获取的数据。函数的输出可能是返回值、输出参数,也可能是向其他模块提供的数据。
由于单元测试针对程序单元,而程序单元并不是一个独立可运行的程序,往往需要系统API,或者其他模块提供动态库或网络通讯支持,因此,在考虑测试模块时,同时要考虑到它和外界其他模块的联系,用一些辅助模块去模拟与被测模块关联的其他模块。
所以单元测试代码结构上分为两种,一种是桩代码,即模拟其他模块的代码,桩代码可以提供函数接口、初始化数据等信息,用于模拟外部模块接口。另一种是测试代码,测试代码通过调用被测函数观察结果是否符合预期。
若发现对某个函数进行测试时,需要做大量的桩代码来做输入的模拟,或者检查输出时需要检查的都是其他模块中的信息,则说明对应函数的耦合度太高,需要考虑将对应函数的功能和其他模块剥离,其他模块提供的数据作为函数的参数输入,函数参数或返回值输出结果,其他模块从函数外部获取对应的数据。
正式发布的代码结构
单元测试的代码结构
单元测试代码需要和发布代码隔离,不能污染发布代码,不能在发布代码中充斥有大量的单元测试代码,这样不但后续代码开发维护人员理解单元测试用例比较困难,而且容易造成在发布版本中错误的调用了单元代码,出现功能异常。一般可以通过新增加一个单元测试工程,使用工程的编译宏来控制不同的头文件以达到隔离效果。
单元测试用例的设计
单元测试用例设计思路可以从两个方面进行,一个是功能实现相关,即从外部进行设计,考虑对应的模块要实现什么功能,每个功能会有什么输入输出。做到功能覆盖,在发布代码编写前后进行都可以。另一个是内部逻辑相关,即从内部考虑,从发布代码的逻辑分支进行设计,做到分支覆盖。在发布代码编写后进行。
具体对某个单元进行测试时,可以按照优先级,以下步骤进行:
第一步设计基本功能测试用例,检查被测单元至少在需求功能级别能够返回期望的结果;
第二步设计功能正面测试用例,检查被测单元对于设计要求的正确输入需要返回期望结果;
第三步设计功能反面测试用例,检查被测单元对于设计要求的错误输入需要返回期望结果;
最后一步设计性能测试用例, 检查被测单元在大量数据情况下的执行效率。
单元测试的目的是对函数功能的测试,所以测试时并不是需要对所有函数进行测试,需要进行选择,如果对应的函数中有较为复杂的逻辑代码,则必须测试,如果函数中基本上没有什么逻辑,则没有必要测试。
测试代码编写时需要注意代码的可维护性以及可读性,对于重复出现的代码,例如构造初始环境等尽量抽取程函数或者宏,而对于某一项测试尽量做到封装成一条语句。必要时增加注释信息,尽量做到从单元测试代码即可知道要测试什么特性,对应特性输入、输出是什么。
单元测试成本和效率
单元测试不可避免的增加了开发的工作量,但是按定义,单元测试只测试程序单元自身的功能。因此,它不能发现集成错误、性能问题、或者其他系统级别的问题。单元测试只能表明测到的问题,不能表明不存在未测试到的错误。所以单元测试并不能代替系统测试的工作。
单元测试的意义在于对于异常功能的测试,以及对于后续代码维护开发的保障。