为什么TDD成为PHP开发者的标准?
想象一下:您编写代码,运行它,看到错误,修复它——然后如此循环。熟悉吗?现在想象一下,您先编写一个注定失败的测试,然后编写最少的代码使其通过,最后才进行重构。这就是TDD在PHP中——测试驱动开发,它将混乱转变为有序的系统。
许多开发者害怕TDD:“这会拖慢开发速度!”,“我不知道如何测试遗留代码”,“PHPUnit很复杂”。但实际上,TDD节省了数小时的调试时间,并使代码能够抵御变化。在本文中,我们将通过真实的PHP示例来剖析TDD,展示“红-绿-重构”循环如何工作,并提供现成的实施模板。
什么是TDD,它在PHP中如何工作?
测试驱动开发(Test-Driven Development)是一种在实现之前编写测试的方法论。基本循环如下:
- 红色——编写一个不通过的测试(预期错误)。
- 绿色——编写最少的代码使测试通过。
- 重构——改进代码,不改变行为。
在PHP中,TDD最常使用PHPUnit——一个强大的单元测试框架。但TDD关乎的是纪律,而非工具。让我们看看它在实践中如何工作。
示例1:测试一个简单的计算器
假设我们需要一个两数相加的函数。没有TDD,我们会这样写:
function add($a, $b) { return $a + $b;}echo add(2, 3); // 5使用TDD,我们首先编写测试:
use PHPUnit\\Framework\\TestCase;
class CalculatorTest extends TestCase { public function testAdd() { $calculator = new Calculator(); $result = $calculator->add(2, 3); $this->assertEquals(5, $result); }}运行——测试失败(红色)。编写最少的实现:
class Calculator { public function add($a, $b) { return $a + $b; }}测试通过(绿色)。不需要重构——代码很简单。但如果我们想添加对负数的处理,我们会先为这种情况编写测试。
示例2:使用TDD进行数据库操作
人们常说:“TDD不适用于数据库”。这是误解。使用模拟(mocks)和测试数据库。来看一个UserRepository的示例:
class UserRepositoryTest extends TestCase { public function testFindUserById() { $pdoMock = $this->createMock(PDO::class); $stmtMock = $this->createMock(PDOStatement::class); $pdoMock->method('prepare') ->willReturn($stmtMock); $stmtMock->method('fetch') ->willReturn(['id' => 1, 'name' => 'Alex']); $repository = new UserRepository($pdoMock); $user = $repository->findById(1); $this->assertEquals('Alex', $user['name']); }}测试验证了仓库正确调用了SQL并处理了结果。实现代码将是最少的:只需执行查询并返回数据。
PHP中TDD的流行工具
要使TDD成为您工作流程的一部分,您需要掌握几个工具:
- PHPUnit——主要的测试框架。安装:
composer require --dev phpunit/phpunit。 - Mockery——用于创建具有灵活API的模拟对象的库。
- Prophecy——Mockery的替代品,常与Symfony一起使用。
- PHPStan / Psalm——静态分析工具,通过发现运行测试前的错误来补充TDD。
对于集成测试,可以使用Laravel Dusk或Symfony Panther,但作为开始,足够