1、单元测试基础:什么是单元测试、为什么需要单元测试、PHPUnit简介与安装

各位同学,咱们今天聊聊单元测试。说实话,我刚开始写PHP那会儿,也觉得测试是浪费时间。直到有一次,我负责的一个电商项目上线前,手动改了一个支付接口的参数,结果把整个订单流程搞崩了。那天晚上我盯着屏幕到凌晨三点,一边改bug一边骂自己——为什么不写测试?

从那以后,我成了测试驱动开发的忠实信徒。今天这第一课,咱们就把单元测试的底裤扒干净,看看它到底是什么玩意儿,为什么非学不可。

什么是单元测试?

单元测试,说白了就是给代码做「体检」。你写了一个函数、一个方法,或者一个类,单元测试就是单独拎出来,检查它能不能按预期工作。

举个例子。你写了个计算折扣的函数:

function calculateDiscount($price, $isVip) {
    if ($isVip) {
        return $price * 0.8;
    }
    return $price * 0.9;
}

单元测试会这样测:

// 测试普通用户
$result = calculateDiscount(100, false);
assert($result === 90);  // 打九折,应该是90

// 测试VIP用户
$result = calculateDiscount(100, true);
assert($result === 80);  // 打八折,应该是80

嗯,就是这么简单。每个测试只关注一个「单元」,不依赖数据库、不依赖外部API、不依赖其他类。我习惯把单元测试想象成「隔离审查」——只审这一个模块,其他的一概不管。

核心要点:单元测试的「单元」通常是一个方法或函数。它应该是独立的、可重复的、自动化的。

为什么需要单元测试?

你可能会问:「我手动测一下不就行了?干嘛费劲写代码测代码?」

好问题。我跟你讲讲我的血泪史。

我曾经维护过一个老项目,代码量大概20万行。每次改一个地方,都要手动跑一遍所有功能。改个登录,要测注册、测找回密码、测权限……一天下来,真正写代码的时间不到两小时,全在点鼠标了。

单元测试能解决三个核心痛点:

  • 回归测试自动化——改完代码,跑一遍测试就知道有没有搞坏别的地方。不用再手动点来点去。
  • 代码设计更合理——你想想看,如果一个方法很难写测试,那它八成设计得有问题。耦合太高、职责不单一,这些都是坏味道。
  • 文档即代码——测试用例本身就是最好的使用文档。新同事接手,看看测试就知道这个类该怎么用、边界条件是什么。

我的经验:我曾经接手过一个项目,没有一行测试代码。我花了整整两周,才敢动第一个文件。后来我强制团队写单元测试,三个月后,发布新版本的效率提升了至少三倍。

还有一个很重要的点——信心。有单元测试兜底,你改代码的时候心里有底。没有测试,每次上线都像在走钢丝。你想想看,哪个更舒服?

PHPUnit简介

PHPUnit是PHP生态里最主流的单元测试框架。它由Sebastian Bergmann创建,目前已经发展到第10版。我用了快十年,从3.x版本一路跟过来,看着它越来越成熟。

PHPUnit能做什么?

  • 自动发现并运行测试
  • 提供丰富的断言方法(assertEquals、assertTrue、assertNull等)
  • 支持测试夹具(setUp、tearDown)
  • 支持数据提供器(@dataProvider)
  • 生成代码覆盖率报告
  • 支持模拟对象(Mock对象)

说白了,PHPUnit就是给你搭好了架子,你只需要往里填测试逻辑就行。

安装PHPUnit

安装方式有两种,我建议你用Composer,这是目前最规范的做法。

方式一:全局安装(推荐)

composer global require phpunit/phpunit

装完之后,在终端里输入 phpunit --version,能看到版本号就说明成功了。

方式二:项目内安装

composer require --dev phpunit/phpunit

这种方式会把PHPUnit装到项目的vendor目录下,通过 ./vendor/bin/phpunit 来运行。

注意:我曾经遇到过一个问题——全局安装的PHPUnit版本和项目依赖的版本冲突。后来我统一用项目内安装,配合composer.json锁定版本,再也没出过问题。建议你也这么做。

装好之后,咱们写个最简单的测试验证一下:

<?php
use PHPUnit\Framework\TestCase;

class FirstTest extends TestCase
{
    public function testAddition()
    {
        $result = 1 + 1;
        $this->assertEquals(2, $result);
    }
}

保存为 FirstTest.php,然后运行:

./vendor/bin/phpunit FirstTest.php

看到绿色的「OK (1 test, 1 assertion)」了吗?恭喜你,第一个单元测试跑通了。

PHPUnit的核心概念

咱们快速过一下几个关键概念,后面会详细展开:

概念 说明
测试用例 继承TestCase的类,包含多个测试方法
测试方法 以test开头的方法,包含断言逻辑
断言 assertEquals、assertTrue等,验证预期结果
测试夹具 setUp和tearDown,在每个测试前后执行
数据提供器 @dataProvider注解,用多组数据跑同一个测试

嗯,这里要注意——测试方法必须是public的,而且不能有参数(除非用了数据提供器)。我刚开始学的时候,老忘记加public,结果测试跑不起来,找了半天才发现。

小结

今天咱们把单元测试的底子打好了。你知道了:

  • 单元测试是给代码做「体检」,每个测试只关注一个单元
  • 它能帮你做回归测试、改善代码设计、充当活文档
  • PHPUnit是PHP界最主流的测试框架,用Composer安装最靠谱

下一章,咱们会深入讲怎么写第一个真正的测试用例,包括断言怎么写、测试怎么组织。到时候我会拿一个真实项目里的例子来演示,保证你学完就能用上。

记住一句话:没有测试的代码,就像没有安全带的赛车——跑得快,但死得也快。

公众号:蓝海资料掘金营,微信deep3321