開發過程中常遇到:功能完成後 Bug 不斷、修一個壞一個、重構不敢動。單元測試是解決這些痛處的基礎工程實踐。
# 安裝 composer require --dev phpunit/phpunit # 執行 ./vendor/bin/phpunit --bootstrap vendor/autoload.php tests/
<?php
// src/Calculator.php
class Calculator {
public function add(float $a, float $b): float { return $a + $b; }
public function div(float $a, float $b): float {
if ($b == 0) throw new InvalidArgumentException('除數不可為零');
return $a / $b;
}
}
// tests/CalculatorTest.php
use PHPUnit\Framework\TestCase;
class CalculatorTest extends TestCase {
private Calculator $calc;
protected function setUp(): void {
$this->calc = new Calculator();
}
public function testAdd(): void {
$this->assertEquals(5, $this->calc->add(2, 3));
$this->assertEquals(0.3, $this->calc->add(0.1, 0.2), '', 0.001); // 浮點數容差
}
public function testDivByZero(): void {
$this->expectException(InvalidArgumentException::class);
$this->calc->div(10, 0);
}
/**
* @dataProvider addProvider
*/
public function testAddDataProvider(float $a, float $b, float $expected): void {
$this->assertEquals($expected, $this->calc->add($a, $b));
}
public function addProvider(): array {
return [
[1, 2, 3],
[0, 0, 0],
[-1, 1, 0],
[1.5, 2.5, 4.0],
];
}
}
?>
| 原則 | 說明 |
|---|---|
| F.I.R.S.T | Fast(快)、Independent(獨立)、Repeatable(可重複)、Self-validating(自我驗證)、Timely(及時) |
| AAA 模式 | Arrange(準備)、Act(執行)、Assert(斷言) — 每個測試結構清晰 |
| 單一職責 | 一個測試只驗證一個行為/邊界條件 |
| 命名規範 | test[Method]_[Scenario]_[Expected] 如 testDiv_ZeroDivisor_ThrowsException |
# 產生覆蓋率報告 ./vendor/bin/phpunit --coverage-html coverage-report # GitHub Actions 片段 - name: Run Tests run: ./vendor/bin/phpunit --coverage-clover=coverage.xml
❌ 測試實作細節而非行為(如斷言私有方法被呼叫)
❌ 測試間有共享狀態(資料庫未清理、靜態變數殘留)
❌ 追求 100% 覆蓋率卻忽略邊界條件
✅ 關注業務關鍵路徑、邊界值、異常流程
✅ 使用setUp/tearDown隔離測試環境
✅ 外部依賴用 Mock/Stub 隔離
單元測試不是成本,而是長期維護速度的投資。從核心領域模型開始寫起,漸進擴大覆蓋。