突變測試

入門

需要 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 會

  • 僅執行涵蓋變異程式碼的測試,以加快處理時間。
  • 盡量快取,以加快後續執行時處理時間。
  • 如果啟用,請使用平行執行以 paralel執行多個測試,以加快處理時間。

已測試 vs. 未測試的變異

執行變異測試時,你將「主要」看到兩個類型的變異:已測試未測試的變異。

  • 已測試的變異:這些變異會被你的測試組件檢測到。它們被視為「已測試」,因為你的測試能夠捕捉到變異導入的變更。

例如,以下變異被視為「已測試」,因為測試組件能夠檢測到變更。

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 為給定 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 的異動測試功能是一項強大的工具,可提升測試套件品質。在下一章節中,我們將說明如何使用快照來測試您的程式碼:快照測試