1. 基础环境搭建与安装
1.1 运行环境准备与依赖管理
本节帮助你为 PHPUnit 实战教程 打好基础,明确需要的运行环境、依赖管理方式以及测试框架的版本选择。PHP 版本、Composer 的安装与使用,以及如何确保开发环境与生产环境一致,是后续单元测试稳定性的关键。下面的要点将引导你从零开始搭建一个可测试的项目基础。
首先确认你的环境具备 PHP 7.4 及以上、Composer,并且具备对外部包的网络访问能力。接着,在项目根目录初始化一个新的包管理配置,并将 phpunit 设为开发依赖。
# 初始化项目
mkdir my-php-project && cd my-php-project
composer init --name="example/phpunit-tutorial" -n# 安装 PHPUnit(开发依赖)
composer require --dev phpunit/phpunit ^9.5
为确保测试配置一致,建议添加一个 phpunit.xml 或 phpunit.xml.dist 配置文件,描述测试用例路径、日志输出以及覆盖率选项。配置文件 结合 命名约定,能够让团队快速定位测试范围与执行行为。
./tests
1.2 基础项目结构与测试目录约定
在进行 从零开始的单元测试实践 时,明确的项目结构能够提升可维护性。通常的约定是将生产代码放在 src 目录,将测试用例放在 tests 目录,并在 composer.json 的 autoload 中配置自动加载。
为了便于持续集成与本地快速执行,建议在源码中实现一个简单的目录结构,确保 命名空间 与 自动加载 设置一致,并在测试中使用 TestCase 基类进行统一化处理。
2. PHPUnit 基本概念与第一步测试
2.1 测试用例结构与断言类型
在本小节中,我们聚焦于理解 PHPUnit 的基本构成:测试用例类、测试方法、以及一系列 断言 用于验证行为。掌握这些基础,将为后续的实践案例打下坚实基础。

一个典型的测试用例类需要继承自 PHPUnit\Framework\TestCase,测试方法通常以 test 开头,便于框架自动发现。常见的断言包括 assertEquals、assertTrue、assertContains 等,覆盖多种断言场景。
assertEquals(5, $calculator->add(2, 3));}
}
运行测试的基本命令是 vendor/bin/phpunit,若未指定测试路径,默认会扫描 tests 目录下的测试用例。通过这样的流程,你可以快速验证 实现行为与预期一致 的结果。
3. 实战案例:从零开始的测试实践
3.1 案例一:Calculator 计算器的单元测试
在这个案例中,我们以一个简单的 Calculator 类为对象进行单元测试,覆盖加减乘除等基本运算,以及边界情况的处理。核心目标是让你理解 测试驱动开发 的基础路径:先编写测试再实现功能,逐步验证正确性。
通过以下代码,我们实现一个最小可用的 Calculator 类,以及对应的 PHPUnit 测试用例。测试优先级与实现耦合度 的关系在这里初步显现,帮助你在后续练习中优化设计。
assertEquals(5, $calc->add(2, 3));}public function testSub(){$calc = new Calculator();$this->assertEquals(1, $calc->sub(3, 2));}
}
3.2 案例二:用户注册服务的单元测试
在实际项目中,用户注册往往涉及表单校验、密码强度、邮箱发送等行为。我们通过一个简单的 UserService 来演练:输入校验、重复注册检测、以及对外部依赖的最小 mock 使用。
以下代码演示如何对一个基础注册服务进行单元测试,在测试中可以通过 Mock 外部依赖(如邮件服务)来聚焦业务逻辑本身。
emailSender = $emailSender;}public function register($user){if (empty($user['email']) || empty($user['password'])) {throw new InvalidArgumentException("Missing credentials");}// 假设检查邮箱唯一性等逻辑$this->emailSender->sendWelcome($user['email']);return true;}
}
getMockBuilder('EmailSender')->getMock();$mailer->expects($this->once())->method('sendWelcome')->with($this->equalTo('test@example.com'));$service = new UserService($mailer);$this->assertTrue($service->register(['email' => 'test@example.com', 'password' => 'secret']));}
}
3.3 案例三:购物车结算的集成风格单元测试
购物车结算常见的关注点包括商品项汇总、价格计算、税费与折扣等。尽管是“单元测试”,但我们可以通过将核心逻辑拆分到可替换的组件来实现更接近集成测试的场景。下面展示一个简单的购物车结算场景,重点在于如何使用数据提供者(DataProvider)来覆盖多种输入组合。
利用数据提供者,我们能够为同一测试方法提供多组数据,从而避免重复编写大量测试用例。
assertEquals($expected, $cart->total($items));}public function itemProvider(){return ['single item' => [[ ['price' => 10, 'qty' => 2] ], 20],'multiple items' => [[ ['price' => 5, 'qty' => 3], ['price' => 7, 'qty' => 2] ], 31],];}
}
4. 高级主题与持续集成中的 PHPUnit
4.1 Mock 对象与依赖注入的高级用法
在真实项目中,依赖注入 与 Mock 对象 的使用是实现高可测试性的关键。通过对外部依赖进行模拟,我们能够专注于核心逻辑的正确性,而不受外部系统波动的影响。
下列对比演示了如何使用 createMock 和 getMockBuilder 来构建不同粒度的模拟对象,以实现对复杂交互的单元测试。
createMock(EmailSender::class);
$mailer->expects($this->once())->method('sendWelcome');// 使用 getMockBuilder 进行更细粒度控制
$service = $this->getMockBuilder(UserService::class)->setConstructorArgs([$mailer])->setMethods(['register'])->getMock();
// 进一步的测试逻辑...
4.2 数据驱动测试与数据提供者的扩展用法
数据提供者是让同一测试方法覆盖更多输入场景的强大工具。通过将输入数据和期望结果分离,测试代码具有更高的复用性与可维护性。强烈建议在涉及多组参数的场景下使用 DataProvider,以提升覆盖率与诊断能力。
assertEquals($total, $shop->applyDiscount($subtotal, $discount));}public function discountProvider(){return [[100, 0, 100],[100, 10, 90],[200, 25, 150],];}
}
4.3 覆盖率分析与持续集成
为了确保代码质量,使用 代码覆盖率 分析是重要的实践。PHPUnit 提供了 coverage 相关的选项,结合 CI/CD 流程,可以在每次构建中自动生成覆盖率报告。你可以在本地执行如下命令查看覆盖率走向:
vendor/bin/phpunit --coverage-text --coverage-html=coverage
将覆盖率报告上传到持续集成系统的工件库中,便于团队成员快速定位未覆盖的逻辑区域,这也是 PHPUnit 实战教程 的典型落地方式之一。


