為什麼要自動化測試?
- 越早發現 Bug,修復成本越低:需求階段 1×,編碼 10×,測試 100×,上線後 1000×。
- 重構安全網:有測試覆蓋,敢於大膽重構、升級依賴、調整架構。
- 文檔化行為:測試代碼即「可執行的規格書」,新成員閱讀測試即懂業務邊界。
- CI/CD 門檻:合併請求必須通過測試才能合入主分支。
從 print 到 assert 的演進
階段 1:手動觀察輸出
$fixture = [];
echo sizeof($fixture) . "\n"; // 0
$fixture[] = 'element';
echo sizeof($fixture) . "\n"; // 1
// 人工看輸出是否為 0 和 1
階段 2:自動比對 + 字串輸出
$fixture = [];
echo (sizeof($fixture) === 0) ? "ok\n" : "not ok\n";
$fixture[] = 'element';
echo (sizeof($fixture) === 1) ? "ok\n" : "not ok\n";
// 輸出 ok/ok,但仍需人工掃描
階段 3:斷言函數 + 異常中斷
function assertTrue($condition, $message = '') {
if (!$condition) {
throw new Exception("Assertion failed: $message");
}
}
$fixture = [];
assertTrue(sizeof($fixture) === 0, '空陣列尺寸應為 0');
$fixture[] = 'element';
assertTrue(sizeof($fixture) === 1, '單元素陣列尺寸應為 1');
// 任一失敗拋出異常,腳本非零退出,CI 可直接判定失敗
PHPUnit 核心概念速覽
| 概念 | 說明 |
| TestCase | 繼承 PHPUnit\Framework\TestCase,每個 public function test* 為一個測試方法。 |
| Fixture | setUp() / tearDown() 準備/清理測試環境(DB、檔案、Mock)。 |
| Assertions | assertEquals、assertSame、assertTrue、assertCount、assertException 等豐富斷言。 |
| Data Providers | @dataProvider 參數化測試,一個方法跑多組輸入輸出。 |
| Mock/Stub | createMock()、createStub() 隔離外部依賴(API、DB、時間)。 |
| Code Coverage | --coverage-html report 產生覆蓋率報告,找出未測試分支。 |
第一個 PHPUnit 測試類
<?php
// tests/ArrayTest.php
use PHPUnit\Framework\TestCase;
class ArrayTest extends TestCase {
public function testEmptyArraySizeIsZero(): void {
$fixture = [];
$this->assertCount(0, $fixture);
$this->assertSame(0, sizeof($fixture));
}
public function testAddingElementIncreasesSize(): void {
$fixture = [];
$fixture[] = 'element';
$this->assertCount(1, $fixture);
}
/**
* @dataProvider sizeProvider
*/
public function testArraySizeWithDataProvider(array $input, int $expected): void {
$this->assertCount($expected, $input);
}
public function sizeProvider(): array {
return [
'empty' => [[], 0],
'one' => [['a'], 1],
'three' => [['a','b','c'], 3],
];
}
}
執行與報告
# 安裝
composer require --dev phpunit/phpunit
# 執行單一測試
./vendor/bin/phpunit tests/ArrayTest.php
# 產生 HTML 覆蓋率報告
./vendor/bin/phpunit --coverage-html coverage tests/
測試組織最佳實踐
- 目錄結構:
tests/Unit、tests/Integration、tests/Feature(Laravel 風格)。
- 命名:
test[Method]_[Scenario]_[Expected],如 testWithdraw_InsufficientFunds_ThrowsException。
- AAA 模式:Arrange(準備)、Act(執行)、Assert(斷言)三段式清晰可讀。
- 快速、隔離、可重複:單元測試不連真 DB、不呼叫真 API,用 Mock/In-memory SQLite。
- 測試金字塔:大量單元測試(底)、適量整合測試(中)、極少 E2E 測試(頂)。
常見反模式避雷
| 反模式 | 症狀 | 修正 |
| 測試相依 | 執行順序影響結果 | 每個測試獨立 Fixture,setUp 重建 |
| 測試內部實作 | 改 private 方法導致測試大量失敗 | 測試公開行為(輸入→輸出/副作用) |
| 斷言過多 | 一個測試 20 行 assert | 拆分為多個聚焦測試 |
| 忽略覆蓋率陷阱 | 100% 覆蓋率但無實際斷言 | 關注分支覆蓋與突變測試 |
原文《PHPUnit袖珍指南之自動測試》重構,補充現代 PHPUnit 9/10 語法、Data Provider、Mock、覆蓋率與測試組織最佳實踐。