为什么要学 PHPUnit?

在上一篇文章里,我们聊到了「还没开始单元测试的你一定很忙」,同时也鼓励大家「一个人就能开始测试」。那么接下来就要进入更实际的操作层面,带你走进 PHP 最常见的测试框架 —— PHPUnit。

很多人对 PHPUnit 的印象是「很复杂、很多设定档、好多断言(assert)方法」。但事实上,只要掌握几个核心概念,就能在短时间内写出第一支测试。就像所有新事物一样,最难的往往是那「踏出第一步」。我们就一起从 0 开始,带你做到 1!


为什么选择 PHPUnit?

  • 社群资源丰富

    PHPUnit 在 PHP 生态系已耕耘多年,有大量的教学资源与社群讨论,一遇到问题很容易查询到解法。

  • 与框架或纯 PHP 都相容

    无论是 Laravel 等主流框架,或是纯 PHP 专案都可以整合 PHPUnit,相当弹性。

  • 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)

    这代表你的两个测试(testAdd 和 testSub)都通过了!

    此时,你已「举出一个实例」来证明自己成功写了测试,也验证了程式运作正常。这份信心很重要,因为很多人都卡在「不知道怎么开始」。


    反例示范:测试失败时该怎么办?

    为了让你感受「测试失败会长什么样子」,我们故意改一下 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 常见断言

  • assertTrue($condition) / assertFalse($condition)检查布林值。
  • assertEquals($expected, $actual)检查预期值与实际值是否相等。
  • assertCount($expectedCount, $array)检查阵列的长度是否符合预期。
  • assertNull($variable)检查变数是否为 null。
  • assertInstanceOf($expectedClass, $object)检查物件是否属于预期的类别。
  • 这只是 PHPUnit 常见断言的冰山一角,它的功能非常丰富。未来若有更复杂的测试场景(例:例外抛出、时间相关测试、mock 物件等),你都可以在官方文件或社群中找到相应做法。


    测试与程式设计的良性循环

    在写测试的过程中,往往能促使我们写出更结构化的程式码,因为「要测试,就得让程式码更容易被呼叫、被拆分」。这是一个良性循环:

    • 写测试 → 想要「好测」,于是去「重构程式、拆成小模组」。
    • 程式结构更好 → 写更多测试也更轻鬆。
    • 好测试 → 开发速度与稳定度都提升。

    就算是小专案,也能透过 PHPUnit 改善品质,让你对自己的程式更有信心。


    结论与后续

    恭喜你!如果你跟着这篇示范,成功跑出你的第一个「单元测试」,那就已经从 0 来到 1 了。

    • 只要你的测试档能正常跑通,就足以证明「你已经会写单元测试」。
    • 要做到大范围测试覆盖、处理更复杂的业务逻辑,还需要不断学习与演进。这篇文章只是带你踏出第一步。

    在下一篇,我们将带领大家进一步进入 Laravel Test 实战。利用 Laravel 已经内建的强大测试工具和辅助方法,让你在框架环境下更轻鬆地测试 Controller、路由、资料库操作等。敬请期待!

    本文重点回顾

  • 安装 PHPUnit:Composer 快速安装。
  • 建立测试档案与范例:一个简单的 MyCalculator 测试,引导你做「Arrange、Act、Assert」。
  • 执行测试、查看结果:了解测试通过(绿灯)与失败(红灯)时的讯息。
  • 常用断言:assertEquals() 等基础用法。
  • 思考测试与程式结构:撰写测试是改善程式架构的好时机。
  • 下一篇(Laravel Test 实战:与框架结合的测试技巧)见!一起继续把测试应用到更真实的专案情境。

    更多实用开发技巧在:詹姆士的软件易开罐