無論在私人的或商業性的專案中,開發人員在找尋解決方案的套件的時候,除了功能是否符合需求以外,程式的品質及穩定度也在考慮的範圍之內。開發人員最怕用到有 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/

PHPUint 設定檔

安裝完套件之後,我們還需要在專案範圍 (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

No tests executed!

拍謝!因為還沒開始寫測試,所以什麼測試都沒被執行哦!
才剛剛把設定檔修改好。準備開始寫第一支測試程式。


第一支測試程式

先回顧一下,這個專案作品的架構。測試程式都會放在 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 提供的斷言方法,如果 $valuetrue 則測試成功,反之則測試失敗。

工欲善其事,必先利其器

能不能每寫好一個方法 (method) 就能馬上測試它,而不是每次都得測試全部。而且完全不想要用命令列下指令,配合參數只為了測試剛剛寫好的那個檔案?

當然可以!

不要羨慕亞洲舞王的時間管理術!時間是擠出來的、省出來的。用可以省時間的工具才是王道呀!善用此原則人人都可以是時間管理大師唷。

輔助工具

在 Visual Studio Code 的擴充套件市集有這麼一個很棒的工具,名字是 Yet Another PHPUnit。它會在每個可測試的類別及方法上方產生一個 Run test 按紐,按下去馬上測試該方法,它的原理是幫忙轉換成相對應的指令,所以我們不需要再浪費時間打指令啦。

測試成功囉!

測試失敗。$valuefalse 的情況,遇上斷言邏輯要求結果為 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 的主題我還沒想到說,不過老話一句,我們明天見。

最後修改日期: 2022-02-06

作者

留言

撰寫回覆或留言

發佈留言必須填寫的電子郵件地址不會公開。