从测试说起

562 阅读5分钟

      由于“祖传代码”、“线上bug频出”等问题,断断续续花了一段时间去思考软件质量的问题。站在开发者的角度,测试也是其中相当重要的一环。那今天来聊聊测试。

      首先应该先给测试一个合理的定义,个人觉得测试应该是一个为了发现错误而执行程序的过程,也就是说,一开始就应该认为程序是存在错误的,而测试是为了找出错误,而不是为了证明该程序没有错误。一次成功的测试是发现了程序中的错误,或者诱导程序发生了错误。所以测试可以被认为是一个拥有破坏性的过程,如果程序员在提测时希望自己所提测的功能少被QA报bug,那么就先抱着“我写的代码肯定有bug”的心态进行一次自测。

      那该如何对自己编写的功能进行测试呢?那就需要构建合理的测试用例,稍后会介绍两种普遍的策略:白盒测试和黑盒测试

      由于程序逻辑组合和产品逻辑组合数量非常之大,所以无法穷举出所有的可能对这些逻辑进行测试,测试用例的设计需要遵守的原则之一就是如何利用最少的测试用例去覆盖尽可能多的场景,尽可能多的发现程序所隐含的错误。

      先来讨论白盒测试,白盒测试对程序的逻辑结构进行检查,从中获取相关数据。白盒测试需要关注的是程序逻辑的覆盖程度。其中逻辑覆盖包括:语句覆盖、判定覆盖、条件覆盖、判断/条件覆盖、多重条件覆盖。借用《软件测试的艺术》里的一个例子来对这些概念进行讲述。 

程序如下:

function whiteBoxTestDemo(a, b, x) {
  if (a > 1 && b === 0) {
    x = x / a  
  }  
  if (a == 2 || x > 1) {
    x += 1
  }
  return x
}


流程图如下:(其中ABCDE为执行路径)

      语句覆盖就是在程序中每一条语句都执行一次,也就是说执行路径为:ACE,则测试用例可以是:a=2,b=0,x=3,但是这样的话如果路径ABD是错误,或者在第一条语句中:a > 1 && b === 0 中“&&”操作是错误的,正确的预期逻辑应该是“||”操作,那么这个测试用例也是无法暴露出错误的。所以说语句覆盖是有很大的不足之处。

      判定覆盖是对每一个判定都至少有一个为真和为假的输出结果,例如对于第一条语句,至少需要两个测试用例使得a > 1 && b === 0 分别为真和为假。 那么对于整个程序来讲,需要覆盖的路径为“ACE 和 ABD” 或者 “ACD 和 ABD”。 那么我 们选择“ACE 和 ABD” 这个组合来构造测试用例,测试用例如下:

  • case1: a=3,b=0, x=3; 
  • case2: a=2, b=1, x=1;

这里case1覆盖了路径:ACE; case2覆盖了路径:ABD。但是路径覆盖依然存在问题,比如未能覆盖所有可能的路径。

      条件覆盖是将一个判断中的每个条件的所有可能的结果都至少执行一遍。在整个程序中,有四个判断:

  1. a > 1;
  2. b === 0; 
  3. a === 2; 
  4. x > 1;

那么就是说需要构建的测试用例需要使得这四个判断能为真和为假。 那么我们可以构造如下测试用例:

  • case1: a=2, b=0, x=4;
  • case2: a=1, b=1, x=1.

这两个测试用例覆盖了上述四个判断为真和为假的情况。 对于case1, 它对应的路径是:ABC; 对应case2, 它对应的路径是:ABD。 但是这样并不能使每条语句都能有为真和为假的情况,比如对应第一条语句, 如果构建的测试用例是:case1: a=2, b=2; case2: a=1, b=0; 虽然这两个case满足条件覆盖的定义,但是对于整条语句来讲, 它总是为假的。解决这个问题的办法就是测试用例既要满足条件覆盖,有需要满足判定覆盖。

      判断/条件覆盖则需要满足: 1. 对每个判定都至少有一个为真和为假的输出; 2. 将一个判断中的每个条件的所有可能的结果都至少执行一遍; 其实就是既需要满足判定覆盖,也需要满足条件覆盖。但是在相应的判断语句中,特定的条件或屏蔽掉其余的条件,导致一些条件无法执行,比如对于第一条语句,如果 a > 1 为假,那么就不会继续执行 b === 0 这个条件判断, 所以 判断/条件覆盖 也不一定能发现所有的逻辑错误。解决这个问题的办法就是多重条件覆盖。

      多重条件覆盖是需要将每个判定中的所有可能的条件组合执行一次。在该程序中,需要覆盖的组合为以下八种:

  1. a > 1, b === 0
  2. a > 1, b != 0
  3. a <= 1, b === 0
  4. a <= 1, b != 0
  5. a === 2, x > 1
  6. a === 2, x <= 1
  7. a != 2, x > 1
  8. a != 2, x <= 1
那么可以构造如下测试用例来满足以上的八个组合
  1. a=2, b=0, x=2 ( 覆盖组合1和5, 对应的路径为:ACD)
  2. a=2, b=1, x=1 (覆盖组合2和6,对应的路径为:ABD)
  3. a=1, b=0, x=2 (覆盖组合3和7,对应的路径为:ABE)
  4. a=1, b=1, x=1 (覆盖组合4和8, 对应的路径为:ABD)

综上,如果在一个程序里,如果只是简单的条件判断,那只需要达到判定覆盖就行,如果是复合条件判断,那么需要做到多重条件覆盖才能尽可能的发现逻辑中所隐含的错误。