PHP 怎么红绿重构

wen PHP项目 4

本文目录导读:

PHP 怎么红绿重构

  1. 什么是红绿重构
  2. 实际示例演示
  3. 完整的TDD工作流
  4. 最佳实践
  5. 常见错误与处理
  6. 工具推荐

我来详细解释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

红绿重构的核心理念:

  1. 先测试后开发 - 让测试失败先"红"
  2. 最小实现 - 只写让测试通过的代码
  3. 小步快跑 - 每次改进一点点
  4. 持续重构 - 在绿色状态下优化设计

关键收益: 更可靠的代码、更好的设计、更少的生产bug、更容易维护、实时文档。

抱歉,评论功能暂时关闭!