突变测试简介-或为什么覆盖率低

This article was originally shared on my personal blog. Please visit the original link to get the full version

我认为可以肯定地说,每个人都讨厌由于某些疯狂的生产错误而在半夜醒来。 防止这些事故发生的最常用策略是对您的代码进行战斗测试。 如此艰难地测试您的代码,也许您不会在凌晨3点醒来。

毫无疑问,自动化测试是当今理性和低错误代码库的基础。 您创建了一组测试(称为测试套件),并通过一次又一次地运行测试套件来确保行为对每次更改(或至少在每次部署之前)都是预期的。 因此,团队通常想知道他们的测试有多好:他们是否需要添加更多测试,或者测试套件已经足够好了?

To help answering that question, code coverage (usually abbreviated to coverage) has been seen as the de facto metric for a long time when trying to assess the quality of the tests. But is it a good metric?

What's coverage

我一直坚决主张拥有带有强大测试套件的代码库,这将使我们在代码投入生产之前就对我们的逻辑是正确的充满信心。 而且,与几乎所有其他人一样,我使用覆盖率作为衡量标准,以了解代码是否需要进行更多测试。

代码覆盖率是测试套件中执行的代码行百分比的度量。

但是很快我就可以了解覆盖率不是一个可靠的指标。

Coverage定义也有一些细微的变化,这些变化着眼于已执行函数,块,路径等的百分比。但是为了简单起见,让我们将定义保持在行数以下。

Why coverage sucks

也许您已经检测到覆盖率有问题。 如果没有,让我们看下面的代码片段:

public boolean biggerThanTen(int x) {
  if(x >= 10) {
    return true;
  } else {
    return false;
  }
}

以及相应的单元测试:

@Test
public boolean myTest() {
  assertTrue(biggerThanTen(11));
  assertFalse(biggerThanTen(9));
}

您能猜出代码覆盖率的价值是什么吗? 是的,100%! 但是您可以很容易地看到我们没有检查边界值:如果我们通过了,将会发生什么10作为论点? 显然,尽管覆盖率达到了100%,我们的测试套件仍存在一个重大缺陷!

但是您是否想看到一个更令人沮丧的示例? :)

@Test
public boolean myTestV2() {
  biggerThanTen(11);
  biggerThanTen(10);
  biggerThanTen(9);
}

该测试涵盖了所有情况并报告了100%的覆盖率,但是...您是否注意到我们完全忘记了断言函数返回值? 相信我,这将不是现实生活中的第一次!

Mutation Testing

到现在为止,您应该确信代码覆盖范围包含一些实际问题。 我们只看到了2种情况,但我敢肯定我们还能找到更多的情况。

那么我们有什么选择呢?

Well, sometime ago I saw a talk around Mutation Testing (MT). Mutation Testing can be seen as the idea of introducing small changes (mutations) on the code and run the test suite against the mutated code. If your test suite is strong, then it should catch the mutation, by having at least one test failing.

MT基于两个假设:

  • 称职的程序员假设指出,由经验丰富的程序员引入的大多数软件故障是由于小的语法错误引起的。耦合效应 asserts that simple faults can cascade or couple to form other emergent faults. 耦合效应 suggests that tests capable of catching first order mutations (single mutation) will also detect higher order mutations (multiple mutations) that contain these first order mutations.
Basic concepts

Before heading into some practical examples, let's just go through some basic concepts of mutation testing:

  • 变异运算子/变异子:mutator是应用于原始代码的操作。 基本示例包括更改'>'由一个运算符'<',替换'和'通过'要么'运算符,例如替换其他数学运算符。突变体:突变体是将突变体应用于实体的结果(在Java中,这通常是一个类)。 因此,突变体是该类的修改版本,将在测试套件执行期间使用。杀死/幸存的变异:针对突变代码执行测试套件时,每个突变体都有2种可能的结果:突变体被杀死或存活。 一种被杀死的突变体表示至少有1个测试因突变而失败。 一种幸存的突变体意味着我们的测试套件无法捕获突变,因此应加以改进。等效突变:事物并不总是白色或黑色。 斑马确实存在! 在突变测试主题上,并不是所有的突变都是有趣的,因为某些突变会导致完全相同的行为。 这些被称为等效突变。 等效突变通常会揭示可能被删除/简化的冗余代码。 只要看下面的例子:
// original version
int i = 2;
if (i >= 1) {
    return "foo";
}

// mutant version
int i = 2;
if (i > 1) { // i is always 2, so changing the >= operator to > will be exactly the same
    return "foo";
}

Mutation Testing for Java

For the Java language, there's an awesome MT framework called PIT. I've been experimenting it over the past few months on a small project, and it has caught a few interesting cases.

为什么PIT很棒? 首先,它具有一个maven插件(我知道,这听起来很基本,但是大多数MT工具仍具有研究风格,并且很难使用)。 其次,它非常高效。 PIT一直致力于使MT变得可用,并且IMHO一直做得相当不错。

To get started with PIŤ on a maven project you just need to add the maven plugin:

<plugin>
    <groupId>org.pitest</groupId>
    <artifactId>pitest-maven</artifactId>
    <version>LATEST</version> <!-- 1.4.5 at the time of writing -->
 </plugin>

然后运行以下命令,即可在项目上使用MT:

mvn org.pitest:pitest-maven:mutationCoverage

检查在生成的HTML报告<base_dir>/target/pit-reports/<date>

对于给定的类,您将看到绿线表示突变体被杀死,或红线表示没有测试覆盖该线或突变体存活。

MT problems

变异测试显然并不完美。 尚未得到广泛使用的最大原因之一是,在更大的代码基础上需要大量的计算能力:许多可能的变体,许多测试,更多的代码可编译等。所有这些增加了运行代码的时间。 突变。 PIT通过以下几个方面的优化来解决此问题:

  • 突变产生突变插入测试选择增量分析

另一个问题是MT可以轻松创建突变体,使您的测试以不感兴趣的方式失败,尤其是如果您不按书进行单元测试时。 例如,如果您使用的是H2数据库,则MT可以更改会使连接失败的配置值。 嘲弄也可能成问题。

幸运的是,PIT允许您指定不应更改的类和方法,并且它支持所有主要的Java模拟框架。 但是其他MT工具可能不如PIT强大。

Extreme mutation

极端突变是另一种简化和提高MT速度的突变测试策略。 它通过将整个方法逻辑替换为可为空的块来表征自身:在Java中,我们无需编写任何代码虚空方法,简单返回null;关于返回对象或返回一些常量的方法的声明。

极端突变的基础如下:

  • 方法是对代码和测试套件进行推理的良好抽象层次;极端突变产生的突变比默认/经典策略少得多;极端突变是很好的初步分析,可以在运行细粒度突变运算符之前增强测试套件。

以下代码段显示了变异前的原始方法:

public Optional<String> getSomething(String baseStr) {
        if(baseStr.length() > 10) {
            baseStr += " -> this had more than 10 chars";
        }

        if(baseStr.startsWith("Why")) {
            baseStr += ", and it was probably a question";
        }

        if (baseStr.contains("<secret code>")) {
            return Optional.empty();
        } else {
            return Optional.of(baseStr);
        }
}

而极端的突变会将其转化为:

public Optional<String> getSomething(String baseStr) {
  return Optional.empty();
}

For PIT, there's an engine that implements extreme mutation: descartes. You should totally try it in your project as a much faster alternative.

Conclusion

代码覆盖率存在一些缺陷,这些缺陷使其无法成为测试套件有效性的真实来源。 尽管如此,它还是一个危险信号:如果覆盖率达到10%,则您显然没有足够地测试您的代码(除非您有大量的样板代码-这也是一个危险信号)。

变异测试已经发展成为一个真正的候选者,它成为评估测试套件质量的实际指标,无视迄今为止代码覆盖范围所占据的宝座。

尽管存在与该概念相关的问题,但PIT等工具一直在创建解决方案,并使MT成为评估测试套件强度的可靠解决方案。

如果您有兴趣在其他编程语言上测试MT,请查看:

其他有用资源列表:

And a presentation I gave at Pixels Camp 2019: https://speakerdeck.com/pedrorijo91/mutation-testing-pixels-camp-2019

from: https://dev.to//pedrorijo91/an-intro-to-mutation-testing-or-why-coverage-sucks-3anp

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值