关闭

测试覆盖(率)到底有什么用?

发表于:2014-1-28 11:06

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:姚若舟    来源:51Testing软件测试网采编

  条件边界变异(Conditionals Boundary Mutator)
  对关系运算(<, <=, >, >=)进行变异,上面第二例子就是这种变异
  反向条件变异(Negate Conditionals Mutator)
  对关系运算(==, !=, <, <=, >, >=)进行变异,例如把“==”变成“!=”
  数学运算变异(Math Mutator)
  对数学运算(+, -, *, /, %, &, |, ^, >>, <<, >>>)进行变异,例如把“+”变成“-”
  增量运算变异(Increments Mutator)
  对递增或者递减的运算(++, --)进行变异,例如把“++”变成“--”
  负值翻转变异(Invert Negatives Mutator)
  对负数表示的变量进行变异,例如把“return -i”变成“return i”
  内联常量变异(Inline Constant Mutator)
  对代码中用到的常量数字进行变异,例如把“int i=42”变成“int i=43”
  返回值变异(Return Values Mutator)
  对代码中的返回值进行变异,例如把“return 0”变成“return 1”或者把“return new Object();”变成“new Object(); return null;”
  无返回值方法调用变异(Void Method Calls Mutator)
  对代码中的无返回值方法调用进行变异,也就是把那个方法调用删除掉,上面的第一个例子就是这种变异。
  有返回值方法调用变异(Non Void Method Calls Mutator)
  对代码中的有返回值函数调用进行变异,也就是接收返回值的变量赋值将被替换成为返回值类型的语言默认值,例如把“int i = getSomeIntValue()”变成“int i = 0”
  构造函数调用变异(Constructor Calls Mutator)
  对代码中的构造函数调用进行变异,例如把“Object o = new Object()”变成“Object o == null”
  测试驱动开发和代码变异测试
  测试驱动开发(TDD)是我推崇和实践的写代码(做设计)方法。我在前面曾经提到,代码变异测试的假设是“实现代码是刚好够通过测试的最简单代码”,而这也是TDD中的重要实践之一。大家可能会问,如果做了TDD,代码变异测试的结果又会如何呢?还会产生学习的机会吗?答案是肯定的,一定会。
  让我们通过例子来看一下。我经常会做一些Kata来练习编程技巧,PokerHands(如上图)就是其中之一(其实大体就是实现梭哈的五张比较规则http://codingdojo.org/cgi-bin/wiki.pl?KataPokerHands)。每次我把Kata做完之后,都会用运行一下代码变异测试(sonar中有插件)。Java的代码变异测试工具有个比较好的叫pitest。下面是我用这个工具跑出来的结果,代码可以在这里找到https://github.com/JosephYao/Kata-PokerHands。
  如大家所见,红色那一行中有一个存活下来的代码变异。而这个代码变异是把“index < CARD_COUNT - 1”中的“<”换成“>”。看上去很不可思议吧,因为进行这样的代码变异意味着整个for循环都不会被执行了,应该不可能没有一个测试失败吧?
  让我们来看一下相关的单元测试。在下面这个测试中有三个assert,它们都是在验证“一对”之间通过对子的点数来比较大小的情况。大家仔细观察就可以发现,其实这三个assert中的牌如果作为High Card(就是比一对小一点的牌组)来比较的话,也都是成立的。这也就是那个代码变异可以存活下来的原因,因为即使忽略了一对之间的比较,通过High Card比较出来的大小关系也是一样的。我从中学到的是,只要把 assertPokerHandsLargerThan("2S 3H 5S 8C 8D","2S 3H 5S 7C 7D")改为 assertPokerHandsLargerThan("2S 3H 5S 8C 8D","2S 3H 9S 7C 7D")就可以清除这个代码变异了。
  从这个例子中可以看到,即使以TDD的方法来写代码,也是无法完全避免出现代码变异存活下来的情况的(当然,存活变异的数量要非常明显的少于不用TDD而写出来的代码)。做过TDD的人可能都有这样的感觉,就是有时很难抑制自己写出复杂代码的冲动(也就是说代码不是“刚好够”的)。有时,即使实现代码是最简单的,也可能因为代码过于直接,就会很“随意”的写出一个让当前代码失败的测试。上面的例子就是这种情况,这样不太“有效”的测试通常在TDD过程中很难意识到,从而给之后的代码维护造成隐患。
  除了上面那个有学习意义的代码变异之外,其实工具还帮我找到了一个“没意义”但存活下来的代码变异。
  这里存活下来的代码变异是指把“index < CARD_COUNT - 2”中的“<”变成“<=”。之所以说这个代码变异没意义,是因为根据代码上下文,在for循环中一定会在index等于CARD_COUNT - 2之前就找到那个三张的点数。因为工具无法理解上下文,所以产生了这个没意义的代码变异(也叫做Equivalent Mutation)。之所以举这个例子,只是想提醒大家不要迷信代码变异测试工具。对于他产生的结果一定去分析和学习,不然很容易走上考核指标的那条不归路。
  小结
  总而言之,测试覆盖这种方法是一种不错的学习手段,可以帮助我们提高代码和测试质量。代码变异测试则比传统的测试覆盖方法可以更加有效的发现代码和测试中潜在的问题,提供更多的学习机会。在这里,我要郑重警告那些妄图把代码变异测试变成一种新的考核指标的管理者们,这样做只会迫使程序员从他的神秘工具箱中找出新的法宝来对付你(比如,修改编译器等等)。
  代码变异测试的概念其实早在30年前就被提出了。之所以到目前为止还没有被业界广泛接纳,一个重要原因是由于需要对每个代码变异反复运行测试。如果不是单元测试(运行速度慢),代码变异测试工具执行时将消耗大量的时间。正因如此,单元测试可能是唯一符合代码变异测试要求的一种测试了。如果你对代码变异测试的历史和发展过程感兴趣的话,你可以参考这篇研究报告http://crestweb.cs.ucl.ac.uk/resources/mutation_testing_repository/TR-09-06.pdf。
33/3<123
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号