变异测试

入门

需要 XDebug 3.0+PCOV

变异测试是一种创新的新技术,它对您的代码进行小的更改(变异)以查看您的测试是否能够捕获这些更改。这确保您彻底测试您的应用程序,而不仅仅是达到代码覆盖率,更多的是关于测试的实际质量。它是识别测试套件中的弱点并提高质量的好方法。

要开始进行变异测试,请转到您的测试文件,并使用 covers() 函数或 mutates 函数明确指定测试覆盖的代码部分。

1covers(TodoController::class); // or mutates(TodoController::class);
2 
3it('list todos', function () {
4 $this->getJson('/todos')->assertStatus(200);
5});

在变异测试方面,coversmutates 函数是相同的。但是,covers 还会影响代码覆盖率报告。如果提供,它会过滤代码覆盖率报告,使其仅包含来自引用代码部分的已执行代码。

然后,使用 --mutate 选项运行 Pest PHP 以启动变异测试。理想情况下,使用 --parallel 选项来加快处理速度。

1./vendor/bin/pest --mutate
2# or in parallel...
3./vendor/bin/pest --mutate --parallel

然后,Pest 将针对“变异”代码重新运行您的测试,并查看测试是否仍然通过。如果测试在针对变异时仍然通过,则表示该测试没有覆盖该代码的特定部分。因此,Pest 将输出变异和代码的差异。

1UNTESTED app/Http/TodoController.php > Line 44: ReturnValue - ID: 76d17ad63bb7c307
2 
3class TodoController {
4 public function index(): array
5 {
6 // pest detected that this code is untested because
7 // the test is not covering the return value
8- return Todo::all()->toArray();
9+ return [];
10 }
11}
12 
13 Mutations: 1 untested
14 Score: 33.44%

一旦您确定了未经测试的代码,就可以编写其他测试来覆盖它。

1covers(TodoController::class);
2 
3it('list todos', function () {
4+ Todo::factory()->create(['name' => 'Buy milk']);
5 
6- $this->getJson('/todos')->assertStatus(200);
7+ $this->getJson('/todos')->assertStatus(200)->assertJson([['name' => 'Buy milk']]);
8});

然后,您可以使用 --mutate 选项重新运行 Pest 以查看该变异现在是否已“测试”并覆盖。

1Mutations: 1 tested
2Score: 100.00%

变异分数越高,您的测试套件越好。100% 的变异分数意味着所有变异都已“测试”,这是变异测试的目标。

现在,如果您看到“未测试”或“未覆盖”的变异,或者变异分数低于 100%,通常意味着您缺少测试您的测试没有覆盖所有边缘情况

我们的插件与 Pest PHP 深度集成。因此,每次引入变异时,Pest PHP 将

  • 仅运行覆盖变异代码的测试以加快处理速度。
  • 尽可能多地缓存以加快后续运行的速度。
  • 如果启用,则使用并行执行来并行运行多个测试以加快处理速度。

已测试与未测试的变异

在运行变异测试时,您将“主要”看到两种类型的变异:已测试未测试的变异。

  • 已测试的变异:这些是被您的测试套件检测到的变异。它们被认为是“已测试的”,因为您的测试能够捕获变异引入的更改。

例如,以下变异被认为是“已测试的”,因为测试套件能够检测到更改。

1class TodoController
2{
3 public function index(): array
4 {
5- return Todo::all()->toArray();
6+ return [];
7 }
8}
9 
10it('list todos', function () {
11 Todo::factory()->create(['name' => 'Buy milk']);
12 
13 // this fails because the mutation changed the return value, proving that the test is working and testing the return value...
14 $this->getJson('/todos')->assertStatus(200)->assertJsonContains([
15 ['name' => 'Buy milk'],
16 ]);
17});
  • 未测试的变异:这些是被您的测试套件未检测到的变异。它们被认为是“未测试的”,因为您的测试无法捕获变异引入的更改。

例如,以下变异被认为是“未测试的”,因为测试套件无法检测到更改。

1class TodoController
2{
3 public function index(): array
4 {
5- return Todo::all()->toArray();
6+ return [];
7 }
8}
9 
10it('list todos', function () {
11 Todo::factory()->create(['name' => 'Buy milk']);
12 
13 // this test still passes even though the return value was changed by the mutation...
14 $this->getJson('/todos')->assertStatus(200);
15});

更改返回值只是许多可能的变异之一。通常,变异可以是返回值的更改、方法调用的更改、方法参数的更改等等。

最低阈值执行

为了确保全面的测试和保持测试质量,必须为变异测试结果设置最低阈值。在 Pest 中,您可以使用 --mutation--min 选项为变异测试分数结果定义最低阈值。如果未满足指定的阈值,Pest 将报告失败。

1./vendor/bin/pest --mutate --min=40

选项和修饰符

运行变异测试时可以使用以下选项和修饰符。

@pest-mutate-ignore

在生成变异时忽略给定的代码行。

1public function rules(): array
2{
3 return [
4 'name' => 'required',
5 'email' => 'required|email', // @pest-mutate-ignore
6 ];
7}

--id

仅运行具有给定 ID 的变异。请注意,您需要提供与原始运行相同的选项。

1./vendor/bin/pest --mutate --id=ecb35ab30ffd3491

--everything

为项目中的所有类生成变异,绕过 covers() 方法。此选项非常占用资源,应与 --covered-only 选项结合使用。

1./vendor/bin/pest --everything --parallel --covered-only

理想情况下,您还可以结合使用 --parallel 选项来加快处理速度。

--covered-only

仅在测试覆盖的代码行中生成变异。

1./vendor/bin/pest --mutate --covered-only

--bail

在第一次遇到未测试或未覆盖的变异时停止变异测试执行。

1./vendor/bin/pest --mutate --bail

--class

为给定的类生成变异。例如 --class=App\Models

1 
2./vendor/bin/pest --mutate --class=App\Models

--ignore

在生成变异时忽略给定的类。例如 --ignore=App\Http\Requests

1./vendor/bin/pest --mutate --ignore=App\Http\Requests

--clear-cache

清除变异缓存并从头开始运行变异测试。

1./vendor/bin/pest --mutate --clear-cache

--no-cache

在不使用缓存变异的情况下运行变异测试。

1./vendor/bin/pest --mutate --no-cache

--ignore-min-score-on-zero-mutations

当没有变异时忽略最低分数要求。

1./vendor/bin/pest --mutate --min=80 --ignore-min-score-on-zero-mutations

--profile

将前十个最慢的变异输出到标准输出。

1./vendor/bin/pest --mutate --profile

--retry

首先运行未测试或未覆盖的变异,并在第一次遇到错误或失败时停止执行。

1./vendor/bin/pest --mutate --retry

--stop-on-uncovered

在第一次遇到未测试的变异时停止变异测试执行。

1./vendor/bin/pest --mutate --stop-on-uncovered

--stop-on-untested

在第一次遇到未测试的变异时停止变异测试执行。

1./vendor/bin/pest --mutate --stop-on-untested

如您所见,Pest PHP 的变异测试功能是一个强大的工具,可以提高测试套件的质量。在下一章中,我们将解释如何使用快照来测试您的代码:快照测试