本文目录导读:

我来详细解释PHP中的红绿重构(Red-Green-Refactor)测试驱动开发实践。
什么是红绿重构
红绿重构是TDD(测试驱动开发)的核心循环:
- 红(Red):先写测试,运行失败
- 绿(Green):编写最小代码让测试通过
- 重构(Refactor):优化代码质量,保持测试通过
实际示例演示
步骤1:红(Red)- 先写测试
<?php
// tests/CalculatorTest.php
use PHPUnit\Framework\TestCase;
class CalculatorTest extends TestCase
{
public function testAdd()
{
$calculator = new Calculator();
$result = $calculator->add(2, 3);
$this->assertEquals(5, $result);
}
public function testSubtract()
{
$calculator = new Calculator();
$result = $calculator->subtract(10, 3);
$this->assertEquals(7, $result);
}
}
运行测试:
phpunit tests/CalculatorTest.php
会得到失败结果(红):
PHPUnit 9.5.0 by Sebastian Bergmann.
E 1 / 1 (100%)
Time: 00:00.023, Memory: 4.00 MB
There was 1 error:
1) CalculatorTest::testAdd
Error: Class 'Calculator' not found
步骤2:绿(Green)- 编写最小代码
<?php
// Calculator.php
class Calculator
{
public function add($a, $b)
{
return $a + $b;
}
public function subtract($a, $b)
{
return $a - $b;
}
}
再次运行测试:
phpunit tests/CalculatorTest.php
现在测试通过(绿):
OK (2 tests, 2 assertions)
步骤3:重构(Refactor)- 优化代码
<?php
// CalculatorRefactored.php
class Calculator
{
/**
* @var array 支持的操作
*/
private const SUPPORTED_OPERATIONS = ['add', 'subtract', 'multiply', 'divide'];
/**
* 执行计算
*
* @param string $operation 操作类型
* @param float $a 第一个数
* @param float $b 第二个数
* @return float
* @throws InvalidArgumentException 不支持的运算时抛出
*/
public function calculate(string $operation, float $a, float $b): float
{
if (!in_array($operation, self::SUPPORTED_OPERATIONS)) {
throw new InvalidArgumentException("不支持的运算: {$operation}");
}
return match($operation) {
'add' => $this->add($a, $b),
'subtract' => $this->subtract($a, $b),
'multiply' => $this->multiply($a, $b),
'divide' => $this->divide($a, $b),
};
}
private function add(float $a, float $b): float
{
return $a + $b;
}
private function subtract(float $a, float $b): float
{
return $a - $b;
}
private function multiply(float $a, float $b): float
{
return $a * $b;
}
private function divide(float $a, float $b): float
{
if ($b === 0.0) {
throw new InvalidArgumentException('除数不能为零');
}
return $a / $b;
}
}
完整的TDD工作流
使用Composer设置测试环境
{
"require-dev": {
"phpunit/phpunit": "^9.0"
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
}
}
使用PHPSpec进行行为驱动开发
<?php
// spec/UserSpec.php
namespace spec;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class UserSpec extends ObjectBehavior
{
function it_should_not_be_blank()
{
$this->shouldThrow(\InvalidArgumentException::class)
->during('setName', ['']);
}
function it_should_have_a_name()
{
$this->setName('Alice');
$this->getName()->shouldReturn('Alice');
}
}
最佳实践
1 小而步进(Baby Steps)
// 先写一个简单测试
function testCanCreateBankAccount()
{
$account = new BankAccount();
$this->assertTrue($account->isActive());
}
// 让它通过
class BankAccount {
public function isActive() {
return true;
}
}
// 然后添加更多测试
function testDeposit()
{
$account = new BankAccount();
$account->deposit(100);
$this->assertEquals(100, $account->getBalance());
}
2 使用数据提供器
<?php
use PHPUnit\Framework\TestCase;
class ArrayOperationsTest extends TestCase
{
/**
* @dataProvider arraySumProvider
*/
public function testArraySum($array, $expected)
{
$this->assertEquals($expected, array_sum($array));
}
public function arraySumProvider()
{
return [
[[1, 2, 3], 6],
[[0, 0, 0], 0],
[[-10, 10, 20], 20],
[[0.1, 0.2], 0.3], // 注意浮点数精度问题
];
}
}
3 处理边界条件
<?php
// tests/UserServiceTest.php
use Tests\TestCase;
class UserServiceTest extends TestCase
{
public function testCreateUserWithValidData()
{
$service = new UserService();
$user = $service->createUser([
'name' => 'Alice',
'email' => 'alice@example.com',
'password' => 'secret123'
]);
$this->assertInstanceOf(User::class, $user);
$this->assertNotEmpty($user->getId());
}
public function testCreateUserWithInvalidEmail()
{
$this->expectException(\InvalidArgumentException::class);
$service = new UserService();
$service->createUser([
'name' => 'Alice',
'email' => 'invalid-email',
'password' => 'secret123'
]);
}
public function testCreateUserWithShortPassword()
{
$this->expectException(\InvalidArgumentException::class);
$service = new UserService();
$service->createUser([
'name' => 'Alice',
'email' => 'alice@example.com',
'password' => '123'
]);
}
}
常见错误与处理
错误1:测试太多功能
// ❌ 错误示例
public function testCreateAndUpdateAndDeleteUser()
{
// 测试了三个功能
}
// ✅ 正确示例
public function testCreateUser() { ... }
public function testUpdateUser() { ... }
public function testDeleteUser() { ... }
错误2:实现太多功能
// ❌ 过度实现
class Calculator
{
public function add($a, $b) { return $a + $b; }
public function subtract($a, $b) { return $a - $b; }
public function multiply($a, $b) { return $a * $b; }
public function divide($a, $b) { return $a / $b; }
public function power($a, $b) { return pow($a, $b); }
}
// ✅ 最小实现
class Calculator
{
public function add($a, $b) { return $a + $b; }
}
工具推荐
PHPUnit配置优化
<!-- phpunit.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php"
colors="true"
verbose="true">
<testsuites>
<testsuite name="Unit">
<directory>tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory>tests/Feature</directory>
</testsuite>
</testsuites>
<coverage>
<include>
<directory suffix=".php">src/</directory>
</include>
</coverage>
</phpunit>
持续集成自动化
# .github/workflows/php.yml
name: PHP Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.1'
coverage: xdebug
- name: Install dependencies
run: composer install --prefer-dist --no-progress
- name: Run tests
run: phpunit --coverage-text
红绿重构的核心理念:
- 先测试后开发 - 让测试失败先"红"
- 最小实现 - 只写让测试通过的代码
- 小步快跑 - 每次改进一点点
- 持续重构 - 在绿色状态下优化设计
关键收益: 更可靠的代码、更好的设计、更少的生产bug、更容易维护、实时文档。