测试(2): 怎样做[单元/模块]测试

单元/模块测试在测试体系中的位置

一个项目从开发到里程碑发布,主要分如下几个测试环节(参考:现代软件工程讲义 9 测试 QA 的角色和分工):
testing system

Google的测试博客上有三篇谈如何构建Hackable Projects的文章,里面有一个测试金字塔结构:
testing pyramid

对比之下,我们可以看到,Google的测试工程师实际上把单元测试和模块测试统一归为测试金字塔的Unit这个位置,我们把关注点放在由单元测试和模块测试构成的测试金字塔最底层这块。

单元测试与模块测试的关系

当一个模块里面有两个关键单元,出错的时候,我们可以根据下面的方式来定位:

  1. 错误是由单元1的缺陷导致的?
  2. 错误是由单元2的缺陷导致的?
  3. 错误是由两个单元的缺陷共同导致的?
  4. 错误是由两个单元之间的接口的缺陷导致的?
  5. 错误是由测试代码本身的缺陷导致的?

通过在单元之间做二分法排斥,定位到引起模块错误的缺陷所在的位置。

单元测试的分类

单元测试又有两种类型:侵入型和外置型。

侵入型单元测试:是指在函数内部做的各种Assert、Check,这些代码只会在Debug版或者日志版有效,在发布版里不会出现。

外置型单元测试:是指为一个模块单独建立测试工程,在测试工程里为每个函数每个类专门编写测试函数,测试函数和类的各种可能的调用(测试用例),只有这些测试都通过了,这个模块的健壮性才得到一定程度保证。

单元测试的覆盖率

100%的单元测试覆盖也不能保证消灭所有BUG。譬如日常开发里,多线程的BUG难以调试,由于很多边界条件依赖于特定的线程执行时序才能重现,这部分很多时候都需要靠细致的日志分析和代码复审来定位,如果能重现,就比较易于被修复。

而修复了BUG后,还需要做回归测试。修复BUG有一个不成文的定律:

修复一个BUG引入了3个新BUG,最后一个BUG修复后才把最开始的那个BUG修复。

单元/模块测试的时机

单元测试主要针对最低粒度功能、参数验证,一般针对的是底层的稳定模块,剧烈变动的模块不适合用单元测试。所以很多项目需要做的是模块分离做的比较好,前后端分离做的比较好,依赖并耦合的服务调用之间的分离做的比较好,此时更适合单元测试。做单元测试要根据程序的进度和状态选择合适的时机。如果一个高手开发一个重要模块,如果他的改动本身就是剧烈的试验的,那么只有他自己能设计稳定不变的测试case,其他人不要瞎指挥。反之,可以在模块设计和编写代码的时候就设计和编写符合高内聚、低耦合的模块和代码,这就要求代码保持开放封闭原则:对扩展开放,对修改封闭,具体到面向对象语言里,代码要符合SOLID原则。

单元/模块测试的基本流程

程序也分不同的类型:

  • 命令行程序
  • 桌面GUI程序
  • 网页前端程序
  • 移动设备App
  • Web服务程序
  • 应用内程序,例如微信小程序

不同类型的程序,程序内的模块关系错综复杂,如果没有良好的分层、解耦,可能难以做单元测试。面对一个命令行程序,可能知道如何针对函数写单元测试,然而面对一个移动设备App及其后台服务程序,可能就会不知如何进行单元测试。由于单元测试主要是针对稳定的函数这个粒度,我们可以以这个为目标梳理下单元测试的基本思路:

  1. 请找出重要的函数,对这个函数的所有可能的输入分类,并考虑应该有的输出,编写测试用例进行测试。

  2. 请找出对称操作的函数,比如Push,Pop,对称操作的结果应该是回到最开始的状态,请做单元测试。例如:

    • Assert(list==list.Add(1).Remove(1))
    • Assert(stack==stack.Push().Pop())
  3. 如果是一个类,请创建这个类的实例,并对类的接口做单元测试。

  4. 如果一个类A依赖了其他的类,比如B,那么请创建那些依赖类的模拟实现,比如MockB,只要接口一致,实现上可以直接模拟一些数据返回,这样用来测试目标类A。

  5. GUI的事件代码要和业务的控制代码,以及模型代码分离,例如MVC模式。分离代码后更利于单元测试。

  6. 如果一个操作内部依赖了一些远程过程调用,比如一个Http请求,或者一个本地sqlite操作,那么可以直接创建一些模拟数据,来测试,比如一个Http请求对收到的request应该有某个response,测试的时候可以直接提供一个测试用的模拟函数 someCall(request){return response;}来模拟。同时,对于http请求本身的单元测试,应该在服务端进行,同样的,服务端应该直接构造客户端一样的模拟请求数据,来测试自己的逻辑。 这样做到单元测试的时候,客户端和服务端都不必真正发起远程过程调用。如下图所示:
    testing pyramid

  7. 再根本一点,任何涉及跨进程,跨机器的通信,可以一开始先写一个单进程实现,不需要任何通信就可以跑起来,这样就可以做到AllnOne模式测试。不过这很不容易,例如Chromium支持单进程模式和多进程模式,但是单进程模式总是有各种问题,fake模式最容易在开发过程中过期,除非开发者强力维持fake分支的稳定可用。

posted @ 2016-12-24 23:55  ffl  阅读(1376)  评论(4编辑  收藏  举报