無論在私人的或商業性的專案中,開發人員在找尋解決方案的套件的時候,除了功能是否符合需求以外,程式的品質及穩定度也在考慮的範圍之內。開發人員最怕用到有 Bug 的套件,到時候不是自己動手修,要不回報套件的開發者來修理,都沒辦法解決只好棄用了。
單元測試 (unit testing) 是針對專案程式碼中的最小單位進行測試。最小單位的定義依情況依不同程式語言以及情況而異。不過以 PHP 語言來說,以函式 (function) 及類別的方法 (method) 為最小單位。
雖然說單元測試不是必要,但以套件類型的專案作品來說,是絕對必要。
因為我們要保證發生 Bug 的機率降到最低,唯一的辦法就是每個函式、每個方法、每個類別、甚至在不同版本的 PHP 環境都進行測試。除此之外別無他法。放一包乖乖在電腦上是沒用的 ^^"。
PHPUnit 是在 PHP 生態系中最受歡迎的套件,我們將採用它。但需注意版本相容性。以下為不同版本的 PHPUnit 所支援的 PHP 版本:
PHPUnit 版本 | 支援的 PHP 版本 |
---|---|
7 | 7.1 , 7.2 , 7.3 |
8 | 7.2 , 7.3 |
9 | 7.3 , 7.4 |
PHPUnit 越新的版本提供越多斷言 (assert) 的方法,來減少測試需要寫的程式碼及支援新的 PHP 版本所提供的功能。
因筆者此次在 IT 邦幫忙鐵人賽過程實作的這個範例套件,最低支援的 PHP 是從 7.1 起,所以安裝的的 PHPUnit 是版本 7。
下指令安裝:
composer require --dev phpunit/phpunit ^7
也可以在 Day 15 所提到的相容性指南中,composer.json 中 require-dev
指定 PHPUnit 版本。
composer.json
"require-dev": {
"phpunit/phpunit": "^7"
},
然後下指令安裝:
composer install
接著就可以下指令官方文件的指令列參數表來進行測試。
不加參數的指令:
./vendor/bin/phpunit tests/
安裝完套件之後,我們還需要在專案範圍 (project scope) 的根目錄下放置 PHPUnit 的設定檔。
IT 這條路上,該記的東西實在太多了,如果連下個測試指令還記一堆參數,或者還得查文件,蠻浪費時間的,而且沒計入打指令打錯字浪費的時間 @@
因此 phpunit.xml
就是把一些需要的參數集合在一起的設定檔。當執行 phpunit test
時,PHPUnit 會查找在當前目錄下的 phpunit.xml 檔案。
設定檔官方文件:
https://phpunit.readthedocs.io/en/9.3/configuration.html
(以上為最新文件,舊版請切換至需要的版號文件來閱讀)
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="tests/bootstrap.php" stderr="true" processIsolation="true" colors="true" beStrictAboutTestsThatDoNotTestAnything="false">
<testsuites>
<testsuite name="Shieldon SimpleCache Test Suite">
<directory>tests/SimpleCache/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">src/</directory>
</whitelist>
</filter>
<php>
<ini name="date.timezone" value="UTC"/>
<const name="PHPUNIT_TEST" value="TRUE"/>
</php>
<logging>
<log type="coverage-clover" target="clover.xml"/>
<log type="coverage-html" target="tests/report"/>
</logging>
</phpunit>
<phpunit>
在 <phpunit>
標籤中比較重要的參數為 bootstrap
,指定每次測試時會載入的檔案。我們可以將一些測驗要用的設定、函式、等等,放在這個檔案。
<testsuites>
完整測試時會執行在 <directory>
目錄中的所有和測試相關的檔案。
<filter>
放在 <whitelist>
白名單中的目錄下的檔案才會計算入覆蓋率 (coverage)。
<php>
在這個標籤下可以設定覆寫 php.ini
的設定,以即自訂測試要用的常數,等等。
<logging>
產生的報表,要放置的地方。這些都是測試會產生的臨時檔案,必須從版控中排除。把它們加入 .gitignore
吧!
(註:必須安裝 php_xdebug 才能計算覆蓋率。後面的章節再詳細介紹。)
這麼簡單?把 PHPUnit 安裝起來,放個 phpunit.xml
就可以跑了嗎!?
先來執行 composer test
,觸發一個完整測試。
composer test
composer test
是自訂指令。在 Day 14 發佈 Composer 套件的事前準備一文中,在 composer.json
的設定中有介紹。
D:\terrylin\data\simple-cache>composer test
> php vendor/phpunit/phpunit/phpunit
PHPUnit 7.5.20 by Sebastian Bergmann and contributors.
Time: 436 ms, Memory: 4.00 MB
No tests executed!
Generating code coverage report in Clover XML format ... done
Generating code coverage report in HTML format ... done
拍謝!因為還沒開始寫測試,所以什麼測試都沒被執行哦!
才剛剛把設定檔修改好。準備開始寫第一支測試程式。
先回顧一下,這個專案作品的架構。測試程式都會放在 tests/SimpleCache
下。
.
├── src
│ └── SimpleCache
│ ├── Cache.php
│ ├── CacheProvider.php
│ └── ...
└── tests
└── SimpleCache
├── CacheTest.php
├── CacheProviderTest.php
└── ...
Test
後輟,例如 YourFirstTest.php
。單元測試時,這樣的 檔案 就會被執行。YourFirstTest
。單元測試時,這樣的 類別 就會被執行。test
前輟字串,例如 testYourTestMethod
。單元測試時,這樣的 方法 就會被執行。範例:
/**
* 檔名:YourFirstTest.php
*/
class YourFirstTest extends \PHPUnit\Framework\TestCase
{
public function testYourTestMethod()
{
$value = true;
$this->assertTrue($value);
}
}
每一個測試類別都必須繼承 PHPUnit 的類別,才能使用 PHPUnit 提供豐富的斷言方法。以本例的版本,類別名稱為 PHPUnit\Framework\TestCase
。
其中:
$this->assertTrue($value);
assertTrue
是 PHPUnit 提供的斷言方法,如果 $value
為 true
則測試成功,反之則測試失敗。
能不能每寫好一個方法 (method) 就能馬上測試它,而不是每次都得測試全部。而且完全不想要用命令列下指令,配合參數只為了測試剛剛寫好的那個檔案?
當然可以!
不要羨慕亞洲舞王的時間管理術!時間是擠出來的、省出來的。用可以省時間的工具才是王道呀!善用此原則人人都可以是時間管理大師唷。
在 Visual Studio Code 的擴充套件市集有這麼一個很棒的工具,名字是 Yet Another PHPUnit。它會在每個可測試的類別及方法上方產生一個 Run test 按紐,按下去馬上測試該方法,它的原理是幫忙轉換成相對應的指令,所以我們不需要再浪費時間打指令啦。
測試成功囉!
測試失敗。$value
是 false
的情況,遇上斷言邏輯要求結果為 true
,就會失敗。
設定值
{
"yet-phpunit.phpunitBinary": "php D:\\terrylin\\data\\simple-cache\\vendor\\phpunit\\phpunit\\phpunit --verbose",
"yet-phpunit.xmlConfigFilepath": "D:\\terrylin\\data\\simple-cache\\phpunit.xml"
}
這個工具要設定正確才會運作,記得在 VSCode 設定檔把它設成你的專案目錄相對應的路徑唷。
有的人會覺得說,邊寫測試邊寫程式很麻煩。其實只要找到對的工具,一點都不麻煩。
還好明天是星期六,筆者利用這週末把這個作品寫完,因為還有 WordPress 外掛的設計方法要介紹給大家,戰線不能拖太長。
Day 19 的主題我還沒想到說,不過老話一句,我們明天見。
本文同步更新於 TerryL 部落格 Day 18 - PHP 套件設計實戰 (4) 單元測試,歡迎前往討論。