走進 PHPUnit 的世界:從 0 到 1 的測試入門

更新於 發佈於 閱讀時間約 10 分鐘

為什麼要學 PHPUnit?

在上一篇文章裡,我們聊到了「還沒開始單元測試的你一定很忙」,同時也鼓勵大家「一個人就能開始測試」。那麼接下來就要進入更實際的操作層面,帶你走進 PHP 最常見的測試框架 —— PHPUnit

很多人對 PHPUnit 的印象是「很複雜、很多設定檔、好多斷言(assert)方法」。但事實上,只要掌握幾個核心概念,就能在短時間內寫出第一支測試。就像所有新事物一樣,最難的往往是那「踏出第一步」。我們就一起從 0 開始,帶你做到 1!


為什麼選擇 PHPUnit?

  1. 社群資源豐富
    PHPUnit 在 PHP 生態系已耕耘多年,有大量的教學資源與社群討論,一遇到問題很容易查詢到解法。
  2. 與框架或純 PHP 都相容
    無論是 Laravel 等主流框架,或是純 PHP 專案都可以整合 PHPUnit,相當彈性。
  3. Composer 管理輕鬆安裝
    PHPUnit 的安裝與升級都能透過 Composer 完成,對於現代 PHP 專案來說相當方便。

雖然 PHPUnit 很主流,但也不是唯一選擇。市面上還有像 Pest、Codeception、PHPSpec 等。不同框架、不同專案需求不一,最終還是看你的開發生態做選擇。這裡先以 PHPUnit 作為「從 0 到 1」的首選示範。


安裝與初始化

建立或確認你的專案環境

  • 先準備好一個 PHP 專案目錄(舉例:my-php-project),並確保你的電腦已經安裝 PHP 與 Composer。

透過 Composer 安裝 PHPUnit

在專案目錄下,執行下列指令:

composer require --dev phpunit/phpunit
  • -dev 參數代表這是開發時期使用的套件,不會在正式環境上線時被引入(或會以不同方式處理)。

檢查安裝成功

安裝完成後,你可以在終端機執行:

vendor/bin/phpunit --version

若能看到 PHPUnit 的版本號,表示安裝成功。


寫下你的第一個測試

建立測試資料夾結構

通常我們會在專案裡建立一個 tests 資料夾,來放所有測試程式碼。

  • 結構示例:
my-php-project/
├── src/
| └── MyCalculator.php
├── tests/
| └── MyCalculatorTest.php
└── composer.json

寫個簡單的功能:MyCalculator (示範)

假設我們有一個簡單的類別 MyCalculator,負責做加法、減法。

<?php
// 檔案路徑: src/MyCalculator.php
namespace App;

class MyCalculator
{
public function add($a, $b)
{
return $a + $b;
}

public function sub($a, $b)
{
return $a - $b;
}
}

建立測試檔 MyCalculatorTest

<?php
// 檔案路徑: tests/MyCalculatorTest.php
use PHPUnit\\Framework\\TestCase;
use App\\MyCalculator;

class MyCalculatorTest extends TestCase
{
public function testAdd()
{
// Arrange
$calc = new MyCalculator();

// Act
$result = $calc->add(2, 3);

// Assert
$this->assertEquals(5, $result, '2 + 3 應該等於 5');
}

public function testSub()
{
$calc = new MyCalculator();
$result = $calc->sub(5, 2);
$this->assertEquals(3, $result, '5 - 2 應該等於 3');
}
}

這支測試分為三個常見步驟:

  • Arrange(初始化/準備):建立或準備要測試的物件、參數。
  • Act(執行):呼叫我們要測試的方法。
  • Assert(斷言):檢查結果是否符合預期。

執行測試

回到終端機,在專案根目錄執行:

vendor/bin/phpunit tests

(或僅輸入 vendor/bin/phpunit,它會自動尋找 tests 資料夾。)

如果一切順利,你會在終端機看到類似

PHPUnit x.y.z by Sebastian Bergmann and contributors.

.. 2 / 2 (100%)

OK (2 tests, 2 assertions)

這代表你的兩個測試(testAddtestSub)都通過了!

此時,你已「舉出一個實例」來證明自己成功寫了測試,也驗證了程式運作正常。這份信心很重要,因為很多人都卡在「不知道怎麼開始」。


反例示範:測試失敗時該怎麼辦?

為了讓你感受「測試失敗會長什麼樣子」,我們故意改一下 MyCalculator::sub()

public function sub($a, $b)
{
return $a + $b; // 故意改錯
}

再執行一次測試,你可能會看到:

There was 1 failure:

1) MyCalculatorTest::testSub
Failed asserting that 7 matches expected 3.

這行訊息就很直接地告訴你,預期應該得到 3,實際卻是 7,於是測試失敗。如此一來,你能在開發階段就抓到錯誤,而不必等到功能上線或被 QA 測出來。


更多 PHPUnit 常見斷言

  1. assertTrue($condition) / assertFalse($condition) 檢查布林值。
  2. assertEquals($expected, $actual) 檢查預期值與實際值是否相等。
  3. assertCount($expectedCount, $array) 檢查陣列的長度是否符合預期。
  4. assertNull($variable) 檢查變數是否為 null。
  5. assertInstanceOf($expectedClass, $object) 檢查物件是否屬於預期的類別。

這只是 PHPUnit 常見斷言的冰山一角,它的功能非常豐富。未來若有更複雜的測試場景(例:例外拋出、時間相關測試、mock 物件等),你都可以在官方文件或社群中找到相應做法。


測試與程式設計的良性循環

在寫測試的過程中,往往能促使我們寫出更結構化的程式碼,因為「要測試,就得讓程式碼更容易被呼叫、被拆分」。這是一個良性循環:

  • 寫測試 → 想要「好測」,於是去「重構程式、拆成小模組」。
  • 程式結構更好 → 寫更多測試也更輕鬆。
  • 好測試 → 開發速度與穩定度都提升。

就算是小專案,也能透過 PHPUnit 改善品質,讓你對自己的程式更有信心。


結論與後續

恭喜你!如果你跟著這篇示範,成功跑出你的第一個「單元測試」,那就已經從 0 來到 1 了。

  • 只要你的測試檔能正常跑通,就足以證明「你已經會寫單元測試」。
  • 要做到大範圍測試覆蓋、處理更複雜的業務邏輯,還需要不斷學習與演進。這篇文章只是帶你踏出第一步。

在下一篇,我們將帶領大家進一步進入 Laravel Test 實戰。利用 Laravel 已經內建的強大測試工具和輔助方法,讓你在框架環境下更輕鬆地測試 Controller、路由、資料庫操作等。敬請期待!

本文重點回顧

  1. 安裝 PHPUnit:Composer 快速安裝。
  2. 建立測試檔案與範例:一個簡單的 MyCalculator 測試,引導你做「Arrange、Act、Assert」。
  3. 執行測試、查看結果:了解測試通過(綠燈)與失敗(紅燈)時的訊息。
  4. 常用斷言assertEquals() 等基礎用法。
  5. 思考測試與程式結構:撰寫測試是改善程式架構的好時機。


下一篇(Laravel Test 實戰:與框架結合的測試技巧)見!一起繼續把測試應用到更真實的專案情境。

這是一系列以軟體開發為主題的輕鬆分享,內容涵蓋了技術選擇、開發經驗、實戰應用等多方面的議題。無論是如何在眾多框架中做出選擇,還是如何應對技術轉移的挑戰,這裡有幽默、有趣的對話風格,將複雜的技術問題轉化為易懂的故事。
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
你是不是也有過這樣的經歷:剛修好的功能,過幾天又壞了;每次修改程式碼都得手動測試一遍,還常漏掉影響到的其他地方;整天忙到翻,但最後卻不知道忙在哪裡? 如果這些情境聽起來很熟悉,那麼你可能需要開始了解 單元測試!很多人覺得測試很難,或者認為時間不夠用,其實只要從幾個簡單的步驟開始,一個人也能輕鬆上手。
在一個人資(HR)系統中,薪資結算與保費扣除是最核心的功能之一,扣員工的錢不能多算,該給政府的的也不能少算…。 不管是接手舊系統,還是開發新系統,只要隨著時間推移,每年勞健保的投保級距與費率都可能調整,
「嘿,你有聽說 PHP 8.4 的新特性了嗎?」新特性能在存取屬性時進行驗證、格式化,並支援虛擬屬性設計,程式碼更簡潔易讀,有助於維護和擴展。開發者可透過此特性快速實現自訂邏輯,同時保有工具相容性,讓整體開發流程更順暢、直觀。
CodeIgniter 3 和 Laravel 是兩種不同的 PHP 框架,各有其特點和適用場景。CodeIgniter 3 是一個輕量級框架,Laravel 是一個功能強大的現代 PHP 框架,同樣都有Models的它們有什麼樣的差別呢?
本篇文章為Laravel初學者提供了一個指南,深入探討了Laravel的routes目錄下的功能。文章詳細描述了web.php和api.php的差異和使用情境,並簡要介紹了console.php和channels.php的功能。透過這篇文章,讀者可以更好地理解和利用Laravel的路由功能。
Laravel框架中的路由系統是其核心功能之一,它允許開發者輕鬆地定義應用程式的URL結構和相應的邏輯。本篇文章將深入探討Laravel路由的基本概念、進階功能以及實際應用。
你是不是也有過這樣的經歷:剛修好的功能,過幾天又壞了;每次修改程式碼都得手動測試一遍,還常漏掉影響到的其他地方;整天忙到翻,但最後卻不知道忙在哪裡? 如果這些情境聽起來很熟悉,那麼你可能需要開始了解 單元測試!很多人覺得測試很難,或者認為時間不夠用,其實只要從幾個簡單的步驟開始,一個人也能輕鬆上手。
在一個人資(HR)系統中,薪資結算與保費扣除是最核心的功能之一,扣員工的錢不能多算,該給政府的的也不能少算…。 不管是接手舊系統,還是開發新系統,只要隨著時間推移,每年勞健保的投保級距與費率都可能調整,
「嘿,你有聽說 PHP 8.4 的新特性了嗎?」新特性能在存取屬性時進行驗證、格式化,並支援虛擬屬性設計,程式碼更簡潔易讀,有助於維護和擴展。開發者可透過此特性快速實現自訂邏輯,同時保有工具相容性,讓整體開發流程更順暢、直觀。
CodeIgniter 3 和 Laravel 是兩種不同的 PHP 框架,各有其特點和適用場景。CodeIgniter 3 是一個輕量級框架,Laravel 是一個功能強大的現代 PHP 框架,同樣都有Models的它們有什麼樣的差別呢?
本篇文章為Laravel初學者提供了一個指南,深入探討了Laravel的routes目錄下的功能。文章詳細描述了web.php和api.php的差異和使用情境,並簡要介紹了console.php和channels.php的功能。透過這篇文章,讀者可以更好地理解和利用Laravel的路由功能。
Laravel框架中的路由系統是其核心功能之一,它允許開發者輕鬆地定義應用程式的URL結構和相應的邏輯。本篇文章將深入探討Laravel路由的基本概念、進階功能以及實際應用。
你可能也想看
Google News 追蹤
Thumbnail
大家好,我是woody,是一名料理創作者,非常努力地在嘗試將複雜的料理簡單化,讓大家也可以體驗到料理的樂趣而我也非常享受料理的過程,今天想跟大家聊聊,除了料理本身,料理創作背後的成本。
Thumbnail
哈囉~很久沒跟各位自我介紹一下了~ 大家好~我是爺恩 我是一名圖文插畫家,有追蹤我一段時間的應該有發現爺恩這個品牌經營了好像.....快五年了(汗)時間過得真快!隨著時間過去,創作這件事好像變得更忙碌了,也很開心跟很多厲害的創作者以及廠商互相合作幫忙,還有最重要的是大家的支持與陪伴🥹。  
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
今天就讓我們依照前一天的情境題,來撰寫測試案例函數吧! 這次同樣地,先讓我們規畫擬訂測試案例: 測試案例 使用者註冊: 使用者可送出註冊資料,系統將建立使用者資料,並送出含有專屬驗證連結之驗證信,當此驗證連結被開啟後,將讓使用者轉為已驗證狀態 請求錯誤的驗證連結: 錯誤的驗證連結被開啟後
前兩天,我們探討了「網站文章」的情境題;今明兩天,就讓我們探討另一個情境題「會員註冊」吧! 這邊我們同樣假設網站是採前後端分離的設計,因此我們就專注在測試 API 的部分,不過會多一個「註冊驗證信」的部分要實作與做測試驗證。 使用案例 使用者可填寫註冊資料後送出資料。 使用者可收到註冊驗證信
今天就讓我們依照前一天的情境題,來撰寫測試案例函數吧! 先讓我們規畫擬訂測試案例: 測試案例 當使用者瀏覽文章清單頁時: 使用者可看到所有文章清單,也就是【文章清單API】要能確實將資料庫內的文章資料,筆數不多不少地回應出來。 當使用者瀏覽單一文章頁時: 使用者可看到該文章資料,也就是【
在前面的篇幅中,與大家分享了許多撰寫 PHPUnit 測試程式碼所需的知識,之後的文章就讓我們來來模擬一些情境題,並在這些情境題底下,實際去設計測試案例函數吧! 作為第一個情境題,我們就選「網站文章」來當作第一個挑戰吧! 這邊我們假設網站是採前後端分離的設計,因此我們就專注在測試 API 的部分
今天讓我們來看 phpunit.xml 吧! phpunit.xml 位在 Laravel 專案根目錄底下,顧名思義,它是一個設定 PHPUnit 執行方式的設定XML檔,PHPUnit 提供了不少設定值可供設定,這邊只提最重要的幾個: stopOnFailure 說明:當此欄位設定為 tru
今天讓我們回顧一下前一天的 Mocking 初體驗吧! Mocking 初體驗回顧 app/Repositories/UserRepository.php <?php namespace App\Repositories; use App\Models\User; class UserR
今天要來為大家介紹幾個,在撰寫測試程式碼時可以利用的特殊函數。 setUp() & tearDown() setUp():我們可以在這個函數中,撰寫想要在每個測試案例函數執行前預執行的邏輯。 tearDown():我們可以在這個函數中,撰寫想要在每個測試案例函數執行後預執行的邏輯。 範例:
這一篇讓我們看看幾個重要的 PHPUnit @ Annotation 吧! 所謂的 PHPUnit @ Annotation,是指在測試案例函數前的 PHP Doc 區塊,PHPUnit 提供開發者引用的 @Annotation。PHPUnit 提供的 @ Annotation 大約有 20+ 個
今天會再與大家介紹幾個資料庫 Assertion 函數,與陣列 Assertion 函數。 與前一篇一樣,以下會提到的資料庫 Assertion 函數,並非 PHPUnit 內建,而是由 Laravel 所擴充,因此需注意是否有確實引用到 use Tests\\TestCase 。最後面介紹的2個
前一天我們實作了第一個測試,我們學到了第一個 Assert 函數 assertEquals。 今天讓我們來了解其他常用的 Assert 函數吧! 通用型 Assertion 函數 assertEmpty 函數簽名:assertEmpty(mixed $actual[, string $mess
Thumbnail
大家好,我是woody,是一名料理創作者,非常努力地在嘗試將複雜的料理簡單化,讓大家也可以體驗到料理的樂趣而我也非常享受料理的過程,今天想跟大家聊聊,除了料理本身,料理創作背後的成本。
Thumbnail
哈囉~很久沒跟各位自我介紹一下了~ 大家好~我是爺恩 我是一名圖文插畫家,有追蹤我一段時間的應該有發現爺恩這個品牌經營了好像.....快五年了(汗)時間過得真快!隨著時間過去,創作這件事好像變得更忙碌了,也很開心跟很多厲害的創作者以及廠商互相合作幫忙,還有最重要的是大家的支持與陪伴🥹。  
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
今天就讓我們依照前一天的情境題,來撰寫測試案例函數吧! 這次同樣地,先讓我們規畫擬訂測試案例: 測試案例 使用者註冊: 使用者可送出註冊資料,系統將建立使用者資料,並送出含有專屬驗證連結之驗證信,當此驗證連結被開啟後,將讓使用者轉為已驗證狀態 請求錯誤的驗證連結: 錯誤的驗證連結被開啟後
前兩天,我們探討了「網站文章」的情境題;今明兩天,就讓我們探討另一個情境題「會員註冊」吧! 這邊我們同樣假設網站是採前後端分離的設計,因此我們就專注在測試 API 的部分,不過會多一個「註冊驗證信」的部分要實作與做測試驗證。 使用案例 使用者可填寫註冊資料後送出資料。 使用者可收到註冊驗證信
今天就讓我們依照前一天的情境題,來撰寫測試案例函數吧! 先讓我們規畫擬訂測試案例: 測試案例 當使用者瀏覽文章清單頁時: 使用者可看到所有文章清單,也就是【文章清單API】要能確實將資料庫內的文章資料,筆數不多不少地回應出來。 當使用者瀏覽單一文章頁時: 使用者可看到該文章資料,也就是【
在前面的篇幅中,與大家分享了許多撰寫 PHPUnit 測試程式碼所需的知識,之後的文章就讓我們來來模擬一些情境題,並在這些情境題底下,實際去設計測試案例函數吧! 作為第一個情境題,我們就選「網站文章」來當作第一個挑戰吧! 這邊我們假設網站是採前後端分離的設計,因此我們就專注在測試 API 的部分
今天讓我們來看 phpunit.xml 吧! phpunit.xml 位在 Laravel 專案根目錄底下,顧名思義,它是一個設定 PHPUnit 執行方式的設定XML檔,PHPUnit 提供了不少設定值可供設定,這邊只提最重要的幾個: stopOnFailure 說明:當此欄位設定為 tru
今天讓我們回顧一下前一天的 Mocking 初體驗吧! Mocking 初體驗回顧 app/Repositories/UserRepository.php <?php namespace App\Repositories; use App\Models\User; class UserR
今天要來為大家介紹幾個,在撰寫測試程式碼時可以利用的特殊函數。 setUp() & tearDown() setUp():我們可以在這個函數中,撰寫想要在每個測試案例函數執行前預執行的邏輯。 tearDown():我們可以在這個函數中,撰寫想要在每個測試案例函數執行後預執行的邏輯。 範例:
這一篇讓我們看看幾個重要的 PHPUnit @ Annotation 吧! 所謂的 PHPUnit @ Annotation,是指在測試案例函數前的 PHP Doc 區塊,PHPUnit 提供開發者引用的 @Annotation。PHPUnit 提供的 @ Annotation 大約有 20+ 個
今天會再與大家介紹幾個資料庫 Assertion 函數,與陣列 Assertion 函數。 與前一篇一樣,以下會提到的資料庫 Assertion 函數,並非 PHPUnit 內建,而是由 Laravel 所擴充,因此需注意是否有確實引用到 use Tests\\TestCase 。最後面介紹的2個
前一天我們實作了第一個測試,我們學到了第一個 Assert 函數 assertEquals。 今天讓我們來了解其他常用的 Assert 函數吧! 通用型 Assertion 函數 assertEmpty 函數簽名:assertEmpty(mixed $actual[, string $mess