本文目录导读:

我来详细解释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、更容易维护、实时文档。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播