本文目录导读:

我来帮你系统地处理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();
最佳实践清单
- 从小的、低风险的类开始
- 优先测试经常出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直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播