当前位置: 首页 > 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.jsqmd.com/news/958813/

相关文章:

  • EmbeddingRWKV:革新检索增强生成的线性复杂度架构
  • 长沙配眼镜推荐五家对比,谁家验光准谁家性价比高 - 配眼镜新资讯
  • 昆明配眼镜推荐2026:五家店验光与镜片方案全面测评 - 配眼镜新资讯
  • 免费分享一个站长域名筛选工具:Domain Finder Pro
  • 语言世界模型架构与潜在动作空间优化解析
  • 2026年广州厨房设备回收服务商排行及选型参考:广州上门回收空调/广州中央空调回收/广州回收空调/广州空调回收商家/选择指南 - 优质品牌商家
  • PHP流式处理与生成器应用
  • 如何高效使用ImDisk虚拟磁盘:Windows系统下的全能存储解决方案
  • 告别环境冲突!用Anaconda3虚拟环境独立安装LabelImg(附Qt5配置)
  • 2026昆明配眼镜推荐:五家渠道横向对比与选购思路 - 配眼镜新资讯
  • 当十年前的至强处理器遇上现代大模型:本地推理的极致优化指南
  • 名酒回收联系渠道解析:抚顺市,丹东市,盘锦市,吉林人头马回收/吉林威士忌回收/吉林白兰地回收/吉林轩尼诗回收/哈尔滨名庄红酒回收/选择指南 - 优质品牌商家
  • 别再死记硬背GNN公式了!用‘信息传递’的视角,5分钟图解GCN与GraphSAGE
  • 用C++和pcb-tools搞定Gerber文件解析:一个PCB缺陷检测项目的实战起点
  • 2026年珠片绣口碑排名,哪家更值得选择? - myqiye
  • 2026长沙配眼镜推荐看这篇,五家店从验光到售后全解析 - 配眼镜新资讯
  • 用Python实战马氏性检验:从数据清洗到卡方检验的完整流程(附代码避坑)
  • 2026昆明配眼镜推荐指南:五家配镜渠道深度解析 - 配眼镜新资讯
  • 昆明配眼镜推荐2026实测:五家店配镜真实体验逐一对比 - 配眼镜新资讯
  • 炉石传说脚本自动化:从基础操作到智能决策的完整指南
  • 2026年海关数据平台费用分析,苏维智搜贵吗? - myqiye
  • 别再只会用双线性插值了!PyTorch中nn.Upsample与转置卷积的实战对比与选择指南
  • Veo 2时长限制真相曝光(2024 Q3实测数据+GPU显存占用热力图):超时崩溃前最后37毫秒发生了什么?
  • 重构活动执行基线:营销活动SOP管理工具 2026 的技术内核
  • 别再手动敲Git命令了!用Pycharm 2023.3的图形化界面搞定版本控制(附GitHub配置)
  • 解决AI改文件翻车难题:一套自研沙盒版本机制,让浏览器Agent拥有后悔药
  • 从压缩文件到网络传输:用C++实现哈夫曼编码,并对比string和char*两种方案的性能差异
  • 2026年近期河北沧州钢套钢保温钢管厂家选择指南与优质服务商解析 - 2026年企业资讯
  • 2026年装饰设计品牌企业排名:高性价比的名匠装饰推荐 - myqiye
  • 探寻2026年当下湖南保健品标签优质厂家的核心竞争力:以湖南富林标签为例 - 2026年企业资讯