照片来自 Sangharsh Lohakare,来源于 Unsplash
什么是变异测试?
变异测试是一种用于评估测试套件效果的软件测试技术。它涉及将人工缺陷,即变异,引入到程序的源代码中,然后运行现有的测试套件以确定是否检测到了这些缺陷。
变异测试的主要目标是通过测量其识别这些变异的能力来评估测试套件的质量。
变异测试与代码覆盖率指标有何不同?
与 JaCoCo 等代码覆盖率指标相比,变异测试提供了对测试套件质量的更严格评估。虽然 JaCoCo 测量测试执行代码的程度,但变异测试检查测试检测人工缺陷的能力。
通过引入变异,它评估测试套件在识别这些有意的代码更改方面的有效性,为测试套件能够捕获实际缺陷提供了更高的信心水平。
举例说明
技术规范:
-
JDK 17
-
SpringBoot 2
-
Gradle 现在,让我们添加所需的 pitest 依赖项。
-
在构建文件中添加插件
id 'info.solidsoft.pitest' version '1.7.4'
- 添加 Junit 依赖项
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.2'
- 添加 pitest 对象
pitest {
targetClasses = ['com.example.mutation.controller.*']
outputFormats = ['XML', 'HTML']
timestampedReports = false
junit5PluginVersion = '0.15'
}
让我们通过一个示例来更好地理解变异的概念。
我们将编写一个简单的 API,返回一个字符串,然后使用变异来测试它。
@GetMapping
public ResponseEntity<String> getGreetings(){
return new ResponseEntity<>("Hello", HttpStatus.OK);
}
如果我们只是使用代码覆盖率指标,为了获得良好的代码覆盖率,我们只需要编写这一个测试用例。
@Test
public void testGreetingsResponseWithoutAssert(){
helloController.getGreetings();
}
但是,这个测试用例在 pitest 中会失败。
为什么?
当变异将我们的 API 返回类型更改为 null 时,我们的测试用例将不会失败。 (稍后会详细介绍)
为解决此问题,我们只需将测试用例更改为以下内容:
@Test
public void testGreetings(){
assertNotNull(helloController.getGreetings());
}
现在,当变异返回 null 返回类型时,我们的测试用例将失败,这将通过 pitest。
在项目目录 -> build -> reports -> index.html 下生成了一个 pitest 的覆盖率报告。
样例 pitest 报告
它是如何工作的?
典型的单元测试与 pitest
背后发生的事情是 -
- 将缺陷(变异)引入到我们的程序(在我们的情况下是 API)中,并针对测试用例运行缺陷代码和原始代码。 期望是 - 如果我们的测试用例很好地覆盖了所有情况,那么针对这些测试用例运行的有缺陷的程序(变异体)应该失败。
- pitest 在将其与单元测试进行比较之前,向我们的程序引入了一组默认变异。
一些例子是:
- CONDITIONALS_BOUNDARY
- EMPTY_RETURNS
- FALSE_RETURNS
- INCREMENTS
- INVERT_NEGS
- MATH
- NEGATE_CONDITIONALS
- NULL_RETURNS
- PRIMITIVE_RETURNS
- TRUE_RETURNS
- VOID_METHOD_CALLS
假设我们编写一个测试用例来测试我们的 API -
public void testGreetingsResponse(){
assertEquals("Hello", helloController.getGreetings().getBody());
}
一个 NULL_RETURNS 变异会将我们的 API 更改为以下内容 -
@GetMapping
public ResponseEntity<String> getGreetings(){
return null;
}
在这种情况下,我们的测试用例“testGreetingsResponse()”将会抛出错误,因为将在 null 对象上调用 getBody()。
pitest 只会检查测试用例是否失败,相应的变异体将被杀死。
在 pitest 中,从测试中抛出的异常被视为测试失败。
还要注意,变异体只需要在任何一个测试用例中被杀死,而不是在每个测试用例中都被杀死。
例如,如果我们为我们的 API 编写这两个测试用例,
@Test
public void testGreetingsResponseWithoutAssert(){
helloController.getGreetings();
}
@Test
public void testGreetings(){
assertNotNull(helloController.getGreetings());
}
NULL_RETURNS 变异仍将被杀死,因为 testGreetings() 测试将失败,即使 testGreetingsResponseWithoutAssert() 测试未失败。
如何验证单独的测试类
除了 pitest 生成的常见 index.html 文件外,包含在 pitest 中的每个包也将生成其自己的报告文件,可以从与 index.html 相同的父目录中访问。
有关控制器类的详细报告
在这里,绿色表示成功;如果任何变异体存活下来,那些行将显示为红色。
完整的代码可在 GitHub 上获得,尝试启用/禁用三个测试用例并验证 pitest 结果,以了解其工作原理。
译自:https://medium.com/javarevisited/mutation-testing-with-pitest-and-spring-boot-78ce335cb90c
评论(0)