本文目录导读:

我来详细说明PHP项目中PHPUnit的配置、使用和运行方法。
安装PHPUnit
使用Composer安装(推荐)
# 全局安装 composer global require phpunit/phpunit # 或作为项目依赖 composer require --dev phpunit/phpunit
版本选择建议
{
"require-dev": {
"phpunit/phpunit": "^9.6" // PHP 7.3+
}
}
基本目录结构
project/
├── src/
│ ├── Calculator.php
│ └── UserService.php
├── tests/
│ ├── Unit/
│ │ └── CalculatorTest.php
│ └── Feature/
│ └── UserServiceTest.php
├── phpunit.xml
├── composer.json
└── vendor/
PHPUnit配置文件 (phpunit.xml)
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
verbose="true"
stopOnFailure="false">
<php>
<ini name="error_reporting" value="-1"/>
<env name="APP_ENV" value="testing"/>
<env name="DB_HOST" value="localhost"/>
</php>
<testsuites>
<testsuite name="Unit">
<directory>tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory>tests/Feature</directory>
</testsuite>
</testsuites>
<coverage>
<include>
<directory>src</directory>
</include>
<exclude>
<directory>src/Exceptions</directory>
</exclude>
</coverage>
<logging>
<junit outputFile="build/report.xml"/>
</logging>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">src</directory>
</whitelist>
</filter>
</phpunit>
编写测试类
基础测试示例
<?php
// tests/Unit/CalculatorTest.php
namespace Tests\Unit;
use PHPUnit\Framework\TestCase;
use App\Calculator;
class CalculatorTest extends TestCase
{
private Calculator $calculator;
protected function setUp(): void
{
parent::setUp();
$this->calculator = new Calculator();
}
public function testAddition(): void
{
$result = $this->calculator->add(2, 3);
$this->assertEquals(5, $result);
$this->assertIsInt($result);
}
public function testDivision(): void
{
$result = $this->calculator->divide(10, 2);
$this->assertEquals(5, $result);
}
public function testDivisionByZero(): void
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Cannot divide by zero');
$this->calculator->divide(10, 0);
}
/**
* @dataProvider additionProvider
*/
public function testAddWithDataProvider(int $a, int $b, int $expected): void
{
$result = $this->calculator->add($a, $b);
$this->assertEquals($expected, $result);
}
public function additionProvider(): array
{
return [
[1, 2, 3],
[0, 0, 0],
[-1, -1, -2],
[100, 200, 300]
];
}
}
Mock对象示例
<?php
// tests/Feature/UserServiceTest.php
namespace Tests\Feature;
use PHPUnit\Framework\TestCase;
use App\UserService;
use App\Repositories\UserRepository;
use App\Mail\WelcomeMail;
use Mockery;
class UserServiceTest extends TestCase
{
private UserService $userService;
private $userRepositoryMock;
private $mailerMock;
protected function setUp(): void
{
parent::setUp();
// 创建Mock
$this->userRepositoryMock = $this->createMock(UserRepository::class);
$this->mailerMock = $this->createMock(\App\Mail\Mailer::class);
$this->userService = new UserService(
$this->userRepositoryMock,
$this->mailerMock
);
}
public function testCreateUser(): void
{
$userData = [
'name' => 'John Doe',
'email' => 'john@example.com'
];
$expectedUser = (object) array_merge($userData, ['id' => 1]);
// 设置Mock期望
$this->userRepositoryMock
->expects($this->once())
->method('create')
->with($userData)
->willReturn($expectedUser);
$this->mailerMock
->expects($this->once())
->method('send')
->with($this->isInstanceOf(WelcomeMail::class));
$result = $this->userService->createUser($userData);
$this->assertEquals($expectedUser, $result);
}
public function testGetUserByIdNotFound(): void
{
$this->userRepositoryMock
->expects($this->once())
->method('findById')
->with(999)
->willReturn(null);
$this->expectException(\RuntimeException::class);
$this->userService->getUserById(999);
}
protected function tearDown(): void
{
Mockery::close();
parent::tearDown();
}
}
运行测试
命令行运行
# 运行所有测试 ./vendor/bin/phpunit # 运行特定测试套件 ./vendor/bin/phpunit --testsuite Unit # 运行特定测试文件 ./vendor/bin/phpunit tests/Unit/CalculatorTest.php # 运行特定测试方法 ./vendor/bin/phpunit --filter testAddition tests/Unit/CalculatorTest.php # 生成覆盖率报告 ./vendor/bin/phpunit --coverage-html coverage/
运行指定测试组
class CalculatorTest extends TestCase
{
/**
* @group slow
* @group database
*/
public function testComplexCalculation(): void
{
// ...
}
}
./vendor/bin/phpunit --group slow
常用断言方法
$this->assertEquals($expected, $actual);
$this->assertSame($expected, $actual); // 严格比较
$this->assertTrue($condition);
$this->assertFalse($condition);
$this->assertNull($value);
$this->assertNotNull($value);
$this->assertInstanceOf($class, $object);
$this->assertArrayHasKey($key, $array);
$this->assertCount($expectedCount, $array);
$this->assertStringContainsString($needle, $haystack);
$this->assertMatchesRegularExpression('/pattern/', $string);
高级配置
数据库测试配置
<php>
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
<env name="APP_ENV" value="testing"/>
<env name="CACHE_DRIVER" value="array"/>
<env name="SESSION_DRIVER" value="array"/>
</php>
并行测试
# 使用paratest进行并行测试 composer require --dev brianium/paratest ./vendor/bin/paratest -p4 tests/
常见问题解决
内存不足
php -d memory_limit=-1 ./vendor/bin/phpunit
跳过慢速测试
class CalculatorTest extends TestCase
{
/**
* @requires extension pdo
*/
public function testWithDatabase(): void
{
// ...
}
}
composer.json配置建议
{
"scripts": {
"test": "phpunit",
"test-coverage": "phpunit --coverage-html coverage",
"test-unit": "phpunit --testsuite Unit",
"test-feature": "phpunit --testsuite Feature"
}
}
最佳实践
- 测试命名规范:测试方法名使用
test前缀或@test注解 - 单一断言:每个测试方法只测试一个功能点
- 使用Fixtures:
setUp()和tearDown()方法管理测试资源 - 避免外部依赖:使用Mock对象隔离测试
- 保持测试独立:测试之间不应相互依赖
这样配置后,你就可以通过简单的命令运行PHPUnit测试了,记得定期运行测试确保代码质量。