改善 PHP 測試階段的效能

測試是最重要,但同時也是最花時間的任務,這篇文章將會說明程式測試可以改善效能的方法。

預計看完本篇文章後,可以改善的部分為:

  1. 本機跑測試的時間。
  2. 執行 CI 的時間(參考 Continuous Integration,我們應該要建立 Private Build,因此這件事與第一點是相同的),而 CI 可能又會跟 CD 有關係,也就是可以間接加速交付產品,或是修復錯誤(降低 MTTR)的時間。
  3. 採用短期分支(原文 Short-Lived Feature Branches)通常會在合併前進行測試與程式碼審查(Code Review),以確保合併後的結果是正確的,因此也能間接加速 PR review 時間。

Code Sniffer 檢查調整參數

檢查指令可能原本是:

vendor/bin/phpcs

可以改成:

php -d xdebug.mode=off vendor/bin/phpcs --parallel=$(getconf _NPROCESSORS_ONLN)

調整主要有兩個地方:

  1. 透過 -d xdebug.mode=off 將 xdebug 功能關掉就能加速,但在執行 phpcs 的情境下加速有限(參數詳細可參考 Xdebug 官方說明
  2. 透過 --parallel 參數讓檢查能夠並發處理,其中 $(getconf _NPROCESSORS_ONLN) 可以取得 CPU 的核心數,例如 Macbook M2 有 8 核,「理論上」有幾核就能快幾倍。

以使用 Macbook M1 Pro 10 核實測檢查 1588 個檔案,結果如下:

  • 沒加 parallel 參數 => 159s
  • –parallel=8 => 24s
  • –parallel=10 => 22s
  • xdebug.mod=off + –parallel=10 => 20s

以最快的 20s 來算的話,省下約 87% 的時間。

目前只有試過 squizlabs/php_codesnifferfriendsofphp/php-cs-fixerlaravel/pint 沒試過,但原理是一樣的,應該會有效。

Code Sniffer 本機開發檢查調整

以上是可以同時用在本機開發與 CI 的方法,再來下面是適用在本機開發的方法:

phpcs 支援檢查單一個檔案:

vendor/bin/phpcs app/Foo.php app/Bar.php

如果把新增或修改的檔案透過 git add 加入 stage 後,可以使用下面這個指令找到:

git add app/Foo.php app/Bar.php

git diff --cached --name-only --diff-filter=ACM | grep -e '.php$'
app/Foo.php
app/Bar.php

因此可以把這兩個指令組合在一起,就可以只測「有 git add 的變更檔案」:

vendor/bin/phpcs $(git diff --cached --name-only --diff-filter=ACM | grep -e '.php$')

最後再跟前面的指令組合起來:

php -d xdebug.mode=off vendor/bin/phpcs --parallel=$(getconf _NPROCESSORS_ONLN) $(git diff --cached --name-only --diff-filter=ACM | grep -e '.php$')

要注意的是,這可能只能在本機搭配 git add 使用,另外 grep 的條件可能會需要客製化。

單元測試調整參數

原本可能是這樣測試的:

vendor/bin/phpunit

如果沒有要產 coverage 報表的話,可以把 xdebug 關掉:

php -d xdebug.mode=off vendor/bin/phpunit

實測過的經驗,可以從 345s 縮短到 85s,約省了 75% 的時間。

單元測試控制變數的生命週期

執行測試的過程,如果有使用物件屬性保存資源的話,需要正常回收資源,不然記憶體會用盡(Memory Leak),同時效能也會有一點點差異。

class ApiTest extends TestCase
{
private ?Api $target;

protected function setUp(): void
{
parent::setUp();

$this->target = $this->app->make(Api::class);
}

protected function tearDown(): void
{
// setUp() 裡面有初始化,這裡就需要設定成 null,確保記憶體能夠釋放
$this->target = null;

parent::tearDown();
}
}

以之前的調整經驗,可以從 133s 減到 126s,省約 5% 的時間,雖然省下的時間不多,但可以省非常多記憶體。

省記憶體的另一個角度是有更多記憶體可以做並行測試,因此對加快測試速度還是有幫助的。

Laravel 單元測試控制連線

如果有發現單純執行 php artisan 會連資料庫的話,必須要找出原因在哪,因為這代表每個測試案例在 bootstrap Application 都會連線資料庫,即便該測試案例不需要使用資料庫。

之前曾調整連 Redis 的機制,調整之前 145s ,調整之後 120s,省下 17% 的時間。

寫在最後

還有其他方法可以再加快速度,但程式就需要做更複雜的調整。 只是要回過頭來思考,這一切都是為了讓開發或交付更加快速,以及效率更加快速。(可以參考單元測試文章提到的頻率指標)

如果只是調整參數或程式就能加速測試的話,這 CP 值也實在是太高了,建議大家可以多多嘗試看看。