news 2026/6/22 3:39:06

PHP测试驱动开发与PHPUnit实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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,更重要的是让你敢重构。有测试覆盖的代码,改起来底气足很多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/22 3:38:27

最新点餐源码系统小程序:从单体到Serverless架构升级指南(附代码)

餐饮行业的数字化浪潮正以摧枯拉朽之势席卷而来。扫码点餐、自助结算、私域会员运营——这些曾经的"锦上添花"&#xff0c;如今已成为餐饮门店的生存刚需。而支撑这一切的技术底座&#xff0c;正在经历一场深刻的架构革命&#xff1a;从笨重的单体应用&#xff0c;一…

作者头像 李华
网站建设 2026/6/20 22:34:49

滴滴面试官: 你说熟悉 Agent 记忆机制?那向量库召回不到隐藏因果,你怎么补?长短期记忆怎么拆?我当场沉默了

我的朋友去面滴滴的网约车策略开发岗&#xff0c;面试完后正好在群里找我吐槽了一下&#xff0c;说当场被面试官问得直接没答上来。听完他整个面试过程&#xff0c;我特别有共鸣&#xff0c;也刚好借这件事&#xff0c;跟大家唠唠Agent记忆这块很多人都容易踩的误区和盲区。 他…

作者头像 李华
网站建设 2026/6/21 2:14:06

前后端分离二手商城开发,质检登记、回收回款整套业务源码部署教程

如今二手物品循环交易市场持续成熟&#xff0c;个人闲置售卖、专业物品回收的需求日益增多。传统二手交易模式缺乏标准化流程&#xff0c;物品真伪、成色无官方核验&#xff0c;私下交易回款无记录、售后无保障&#xff0c;个人交易纠纷频发&#xff0c;小型回收商家也存在质检…

作者头像 李华
网站建设 2026/6/21 2:13:12

数据预处理实战:分层防御架构与缺失/异常值决策树

1. 这不是教科书里的“数据清洗”&#xff0c;而是一线工程师每天在Excel、SQL和Python里反复擦汗的真实战场“From Raw to Refined: A Journey Through Data Preprocessing — Part 1”——这个标题乍看像学术论文的副标题&#xff0c;但如果你真在银行风控团队跑过模型上线前…

作者头像 李华
网站建设 2026/6/21 11:23:47

鸿蒙开发--CANNKit-AscendC-sobel

HarmonyOS AscendC 算子&#xff1a;用 NPU 实现图像边缘检测 什么是 AscendC 算子 前面我们介绍了很多图形渲染相关的技术&#xff0c;这篇来看看 AI 领域的东西。AscendC 是华为提供的一种 NPU&#xff08;神经网络处理器&#xff09;编程框架&#xff0c;让你可以自己写算子…

作者头像 李华