PHP 项目遗留代码补测试

wen PHP项目 2

本文目录导读:

PHP 项目遗留代码补测试

  1. 评估现状
  2. 建立测试基础设施
  3. 测试策略
  4. 处理硬编码依赖
  5. 渐进式测试方案
  6. 常用测试技巧
  7. 工具推荐
  8. 最佳实践清单

我来帮你系统地处理PHP遗留代码的测试补充工作,这是一个循序渐进的过程:

评估现状

首先了解代码库的情况:

// 创建测试清单
$files = [
    'legacy_file1.php' => ['complexity' => 'high', 'risk' => 'critical'],
    'legacy_file2.php' => ['complexity' => 'medium', 'risk' => 'high'],
    // ...
];

优先级排序标准:

  • 业务核心功能
  • 频繁修改的文件
  • 有历史bug的代码
  • 与新功能交互的部分

建立测试基础设施

composer.json 配置

{
    "require-dev": {
        "phpunit/phpunit": "^9.5",
        "mockery/mockery": "^1.5",
        "phpunit/phpunit-selenium": "*"
    },
    "autoload": {
        "psr-4": {
            "App\\": "src/",
            "Tests\\": "tests/"
        }
    }
}

phpunit.xml 配置

<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         bootstrap="vendor/autoload.php"
         colors="true"
         stopOnFailure="false">
    <testsuites>
        <testsuite name="Legacy Code Test Suite">
            <directory>tests</directory>
        </testsuite>
    </testsuites>
</phpunit>

测试策略

1 为遗留类创建测试替身

<?php
// tests/Legacy/OrderProcessorTest.php
use PHPUnit\Framework\TestCase;
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
class OrderProcessorTest extends TestCase
{
    use MockeryPHPUnitIntegration;
    public function testProcessOrderWithMockedDatabase()
    {
        // 创建数据库连接的mock
        $dbMock = Mockery::mock('PDO');
        $dbMock->shouldReceive('prepare')
            ->once()
            ->andReturnSelf();
        $stmtMock = Mockery::mock('PDOStatement');
        $stmtMock->shouldReceive('execute')->once();
        $stmtMock->shouldReceive('fetch')
            ->once()
            ->andReturn([
                'id' => 1,
                'total' => 100,
                'status' => 'pending'
            ]);
        $processor = new OrderProcessor($dbMock);
        $result = $processor->processOrder(1);
        $this->assertEquals('success', $result['status']);
    }
}

2 字符集标准化测试

// tests/Legacy/CharacterEncodingTest.php
class CharacterEncodingTest extends TestCase
{
    public function testUtf8Conversion()
    {
        $converter = new LegacyCharacterConverter();
        $utf8String = 'Hello 世界';
        $isoString = mb_convert_encoding($utf8String, 'ISO-8859-1', 'UTF-8');
        $result = $converter->toUtf8($isoString);
        $this->assertEquals('Hello 世界', $result);
    }
}

3 测试全局状态污染

class GlobalStateTest extends TestCase
{
    protected $originalGlobals;
    protected function setUp(): void
    {
        $this->originalGlobals = $_GET;
        parent::setUp();
    }
    protected function tearDown(): void
    {
        $_GET = $this->originalGlobals;
        parent::tearDown();
    }
    public function testFunctionUsingGlobalGet()
    {
        $_GET['user_id'] = 42;
        $controller = new LegacyController();
        $result = $controller->displayUser();
        $this->assertEquals(42, $result['id']);
    }
}

处理硬编码依赖

重构策略 - 引入接口

// 原始代码
class ReportGenerator {
    public function generate() {
        $db = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
        $data = $db->query('SELECT * FROM reports');
        return $data->fetchAll();
    }
}
// 重构后
interface DatabaseInterface {
    public function query($sql);
}
class ReportGenerator {
    private $database;
    public function __construct(DatabaseInterface $database) {
        $this->database = $database;
    }
    public function generate() {
        return $this->database->query('SELECT * FROM reports');
    }
}
// 测试代码
class ReportGeneratorTest extends TestCase {
    public function testGenerate() {
        $dbStub = Mockery::mock(DatabaseInterface::class);
        $dbStub->shouldReceive('query')
            ->once()
            ->with('SELECT * FROM reports')
            ->andReturn([
                ['id' => 1, 'name' => 'Report 1']
            ]);
        $generator = new ReportGenerator($dbStub);
        $result = $generator->generate();
        $this->assertEquals('Report 1', $result[0]['name']);
    }
}

渐进式测试方案

阶段1: 基础测试(第一周)

class SmokeTest extends TestCase {
    // 确保没有语法错误
    public function testAllClassesLoad() {
        $classes = ['Order', 'User', 'Product', 'Cart'];
        foreach ($classes as $class) {
            $this->assertTrue(
                class_exists($class) || interface_exists($class),
                "Class {$class} should exist"
            );
        }
    }
    // 数据库连接测试
    public function testDatabaseConnection() {
        // 只测试是否能加载数据库配置
        $config = include 'config/database.php';
        $this->assertArrayHasKey('host', $config);
        $this->assertArrayHasKey('dbname', $config);
    }
}

阶段2: 持久层测试(第二周)

class LegacyDatabaseTest extends TestCase {
    private $db;
    protected function setUp(): void {
        $this->db = new PDO('sqlite::memory:');
        $this->db->exec("CREATE TABLE orders (id INTEGER PRIMARY KEY)");
    }
    public function testFindOrderById() {
        $orderRepository = new LegacyOrderRepository($this->db);
        $result = $orderRepository->findById(1);
        $this->assertIsArray($result);
    }
}

阶段3: 业务逻辑测试(第三周)

class BusinessLogicTest extends TestCase {
    public function testPriceCalculation() {
        $calculator = new PriceCalculator(
            new LegacyTaxCalculator(),
            new DiscountService()
        );
        $result = $calculator->calculate(100, 'standard');
        $this->assertEquals(120, $result);
    }
}

常用测试技巧

反射测试

class ReflectionTest extends TestCase {
    public function testPrivateMethod() {
        $obj = new LegacyClass();
        $reflection = new ReflectionClass($obj);
        $method = $reflection->getMethod('privateMethod');
        $method->setAccessible(true);
        $result = $method->invoke($obj, 'test');
        $this->assertEquals('expected', $result);
    }
}

特性测试

trait DatabaseFixtures {
    protected function createTestData() {
        // 创建测试数据
    }
    protected function cleanDatabase() {
        // 清理数据库
    }
}

工具推荐

覆盖率分析

phpunit --coverage-html coverage/

静态分析

composer require --dev phpstan/phpstan
vendor/bin/phpstan analyse src --level=max

测试生成器

// 使用PHPUnit's TestCase自动生成
$generator = new \PHPUnit\Util\TestDox\NamePrettifier();

最佳实践清单

  1. 从小的、低风险的类开始
  2. 优先测试经常出bug的功能
  3. 使用依赖注入逐步替换全局依赖
  4. 创建测试夹具时保留原有的行为
  5. 不要试图一次性重构后再测试

补测试是渐进的过程,即使是很简单的测试也比没有测试好,每次修改代码时都添加相应的测试,慢慢积累。

需要我详细解释某个特定场景或提供具体的测试方案吗?

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