当前位置: 首页 > news >正文

PHP测试驱动开发与PHPUnit实践

PHP测试驱动开发与PHPUnit实践

测试是保证代码质量的重要手段。PHPUnit是PHP最流行的测试框架,今天从基础到高级用法都说一遍。

先安装PHPUnit。用Composer安装很方便,composer require --dev phpunit/phpunit。写一个最简单的测试类:

```php
use PHPUnit\Framework\TestCase;

class MathTest extends TestCase
{
public function testAddition(): void
{
$result = 1 + 1;
$this->assertEquals(2, $result);
}

public function testStringConcatenation(): void
{
$result = 'Hello' . ' ' . 'World';
$this->assertEquals('Hello World', $result);
}

public function testArrayPush(): void
{
$arr = [];
array_push($arr, 'item');
$this->assertCount(1, $arr);
$this->assertContains('item', $arr);
}
}
?>
```

测试真实业务类的时候,需要用到setUp方法初始化测试环境。

```php
// 被测试的类
class Calculator
{
public function add(float $a, float $b): float
{
return $a + $b;
}

public function subtract(float $a, float $b): float
{
return $a - $b;
}

public function multiply(float $a, float $b): float
{
return $a * $b;
}

public function divide(float $a, float $b): float
{
if ($b === 0.0) {
throw new InvalidArgumentException('除数不能为0');
}
return $a / $b;
}

public function factorial(int $n): int
{
if ($n < 0) {
throw new InvalidArgumentException('负数没有阶乘');
}
if ($n <= 1) return 1;
return $n * $this->factorial($n - 1);
}

public function isPrime(int $n): bool
{
if ($n < 2) return false;
for ($i = 2; $i <= sqrt($n); $i++) {
if ($n % $i === 0) return false;
}
return true;
}
}

class CalculatorTest extends TestCase
{
private Calculator $calculator;

protected function setUp(): void
{
$this->calculator = new Calculator();
}

public function testAdd(): void
{
$this->assertEquals(5, $this->calculator->add(2, 3));
$this->assertEquals(0, $this->calculator->add(-2, 2));
$this->assertEquals(5.5, $this->calculator->add(2.5, 3.0));
}

public function testDivide(): void
{
$this->assertEquals(2, $this->calculator->divide(10, 5));
$this->assertEquals(3.33, $this->calculator->divide(10, 3), '', 0.01);
}

public function testDivideByZero(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('除数不能为0');
$this->calculator->divide(10, 0);
}

public function testFactorial(): void
{
$this->assertEquals(1, $this->calculator->factorial(0));
$this->assertEquals(1, $this->calculator->factorial(1));
$this->assertEquals(2, $this->calculator->factorial(2));
$this->assertEquals(120, $this->calculator->factorial(5));
}

public function testFactorialNegative(): void
{
$this->expectException(InvalidArgumentException::class);
$this->calculator->factorial(-1);
}

public function testIsPrime(): void
{
$this->assertTrue($this->calculator->isPrime(2));
$this->assertTrue($this->calculator->isPrime(3));
$this->assertFalse($this->calculator->isPrime(4));
$this->assertTrue($this->calculator->isPrime(5));
$this->assertFalse($this->calculator->isPrime(9));
$this->assertTrue($this->calculator->isPrime(11));
$this->assertFalse($this->calculator->isPrime(1));
}
}
?>
```

数据提供器可以用不同的参数多次执行同一个测试:

```php
class DataProviderTest extends TestCase
{
/**
* @dataProvider additionProvider
*/
public function testAdd(int $a, int $b, int $expected): void
{
$result = (new Calculator())->add($a, $b);
$this->assertEquals($expected, $result);
}

public static function additionProvider(): array
{
return [
'正数相加' => [1, 2, 3],
'负数相加' => [-1, -2, -3],
'正负相加' => [5, -3, 2],
'零相加' => [0, 5, 5],
'大数相加' => [1000000, 2000000, 3000000],
];
}

/**
* @dataProvider primeProvider
*/
public function testIsPrime(int $number, bool $expected): void
{
$this->assertEquals($expected, (new Calculator())->isPrime($number));
}

public static function primeProvider(): array
{
return [
[2, true],
[3, true],
[4, false],
[5, true],
[6, false],
[7, true],
[8, false],
[9, false],
[11, true],
[13, true],
];
}
}
?>
```

测试依赖注入和模拟对象:

```php
interface MailerInterface
{
public function send(string $to, string $subject, string $body): bool;
}

class UserService2
{
private MailerInterface $mailer;
private array $users = [];

public function __construct(MailerInterface $mailer)
{
$this->mailer = $mailer;
}

public function register(string $name, string $email): array
{
$user = [
'id' => count($this->users) + 1,
'name' => $name,
'email' => $email,
'created_at' => new DateTime(),
];

$this->users[] = $user;

$this->mailer->send($email, '欢迎注册', "Hello $name!");

return $user;
}

public function getUser(int $id): ?array
{
foreach ($this->users as $user) {
if ($user['id'] === $id) return $user;
}
return null;
}
}

class UserServiceTest extends TestCase
{
public function testRegister(): void
{
$mailer = $this->createMock(MailerInterface::class);
$mailer->expects($this->once())
->method('send')
->with(
$this->equalTo('user@test.com'),
$this->stringContains('欢迎'),
$this->anything()
)
->willReturn(true);

$service = new UserService2($mailer);
$user = $service->register('张三', 'user@test.com');

$this->assertArrayHasKey('id', $user);
$this->assertEquals('张三', $user['name']);
$this->assertEquals('user@test.com', $user['email']);
}

public function testGetUser(): void
{
$mailer = $this->createMock(MailerInterface::class);
$mailer->method('send')->willReturn(true);

$service = new UserService2($mailer);
$service->register('张三', 'a@test.com');
$service->register('李四', 'b@test.com');

$user = $service->getUser(1);
$this->assertNotNull($user);
$this->assertEquals('张三', $user['name']);

$this->assertNull($service->getUser(999));
}
}
?>
```

测试有副作用的代码需要模拟外部依赖:

```php
class DatabaseRepository
{
private PDO $pdo;

public function __construct(PDO $pdo)
{
$this->pdo = $pdo;
}

public function findUser(int $id): ?array
{
$stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = ?');
$stmt->execute([$id]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
return $result ?: null;
}

public function saveUser(string $name, string $email): int
{
$stmt = $this->pdo->prepare('INSERT INTO users (name, email) VALUES (?, ?)');
$stmt->execute([$name, $email]);
return (int)$this->pdo->lastInsertId();
}
}

class DatabaseRepositoryTest extends TestCase
{
public function testFindUser(): void
{
$pdo = $this->createMock(PDO::class);
$stmt = $this->createMock(PDOStatement::class);

$pdo->method('prepare')->willReturn($stmt);
$stmt->method('execute')->willReturn(true);
$stmt->method('fetch')->willReturn([
'id' => 1,
'name' => '张三',
'email' => 'test@test.com',
]);

$repo = new DatabaseRepository($pdo);
$user = $repo->findUser(1);

$this->assertNotNull($user);
$this->assertEquals('张三', $user['name']);
}

public function testFindUserNotFound(): void
{
$pdo = $this->createMock(PDO::class);
$stmt = $this->createMock(PDOStatement::class);

$pdo->method('prepare')->willReturn($stmt);
$stmt->method('execute')->willReturn(true);
$stmt->method('fetch')->willReturn(false);

$repo = new DatabaseRepository($pdo);
$this->assertNull($repo->findUser(999));
}
}
?>

PHPUnit的常用断言:
- assertEquals: 检查是否相等
- assertSame: 检查是否全等(===)
- assertNull/assertNotNull: 检查null
- assertTrue/assertFalse: 检查布尔值
- assertCount: 检查数组长度
- assertContains: 检查是否包含
- assertEmpty: 检查是否为空
- assertInstanceOf: 检查类型

测试不只是为了找bug,更重要的是让你敢重构。有测试覆盖的代码,改起来底气足很多。

http://www.rkmt.cn/news/1470490.html

相关文章:

  • 长沙配眼镜推荐五家对比,谁家验光准谁家性价比高 - 配眼镜新资讯
  • 报销流程繁、对账难、风险高?3 招搞定企业费用管控难题
  • 昆明配眼镜推荐2026:五家店验光与镜片方案全面测评 - 配眼镜新资讯
  • 2026年广州厨房设备回收服务商排行及选型参考:广州上门回收空调/广州中央空调回收/广州回收空调/广州空调回收商家/选择指南 - 优质品牌商家
  • 告别环境冲突!用Anaconda3虚拟环境独立安装LabelImg(附Qt5配置)
  • 2026昆明配眼镜推荐:五家渠道横向对比与选购思路 - 配眼镜新资讯
  • 2026长沙配眼镜推荐看这篇,五家店从验光到售后全解析 - 配眼镜新资讯
  • 用Python实战马氏性检验:从数据清洗到卡方检验的完整流程(附代码避坑)
  • 2026昆明配眼镜推荐指南:五家配镜渠道深度解析 - 配眼镜新资讯
  • 2026年海关数据平台费用分析,苏维智搜贵吗? - myqiye
  • 昆明配眼镜推荐2026实测:五家店配镜真实体验逐一对比 - 配眼镜新资讯
  • 别再只会用双线性插值了!PyTorch中nn.Upsample与转置卷积的实战对比与选择指南
  • Veo 2时长限制真相曝光(2024 Q3实测数据+GPU显存占用热力图):超时崩溃前最后37毫秒发生了什么?
  • PHP正则表达式性能优化指南
  • YOLOv11涨点改进| TGRS 2026 |特征融合改进篇| 引入GFDM全局-局部特征动态融合模块,发论文热点创新,同时关注整体结构和细粒度变化,提升多尺度目标的表达能力,助力目标检测、分割涨点
  • Mybatis中使用表达式错误显示——记录错误
  • TREM2 缺失介导巨噬细胞凋亡调控放射性皮肤损伤创面修复的机制研究
  • 避坑指南:QGC地面站视频流配置失败?从拉流测试到环境变量设置的完整诊断流程
  • 别再为官网下载发愁!CoppeliaSim/V-REP全版本安装包(Win/Mac/Linux)保姆级获取指南
  • 2026年GEO服务商选型必看!十大靠谱GEO源头工厂全维度评测推荐 + 科学避坑指南 - 玖叁鹿
  • 手把手搭建 OpenClaw 智能助手,实现电脑自动化办公操作
  • 如何打造极致便携的Windows C/C++开发环境:w64devkit深度解析
  • HICO-Det数据集深度解析:从‘人骑自行车’到‘人喂斑马’,600种交互背后的标注逻辑与常见坑点
  • 单智能体(Single Agent)落地实践全指南:从 ReAct 到 Tool Use,构建真正可靠的 AI Agent
  • STM32CubeIDE实战:手把手教你配置CAN中断接收,告别轮询死等
  • 免疫组织化学技术实验流程与操作规范详解
  • 海伯森3D线光谱共焦精密测量技术及产业化应用
  • 从手工到自动,不同行业的跨越难点有何异同?2026企业级AI Agent落地全指南
  • 法律文书智能生成系统上线实录(从试点到全所推广仅47天)
  • GBase 8s数据库的四种武器之一,图形化管理平台GEM解析