Prophecy终极指南:7个高效PHP单元测试最佳实践

【免费下载链接】prophecy Highly opinionated mocking framework for PHP 5.3+ 【免费下载链接】prophecy 项目地址: https://gitcode.com/gh_mirrors/pr/prophecy

Prophecy是一个高度专业化但功能强大且灵活的PHP对象模拟框架,专为PHP 5.3+设计。它不仅满足phpspec2的需求,还能轻松集成到任何测试框架中,帮助开发者编写更简洁、可靠的单元测试。

1. 快速入门:Prophecy基础安装与配置 🚀

环境要求

Prophecy需要PHP 7.2.0或更高版本,通过Composer即可快速安装。

一键安装步骤

composer.json中添加依赖:

{
    "require-dev": {
        "phpspec/prophecy": "~1.0"
    }
}

执行安装命令:

composer install --prefer-dist

基础使用模板

use PHPUnit\Framework\TestCase;
use Prophecy\Prophet;

class UserTest extends TestCase
{
    private $prophet;

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

    protected function tearDown(): void
    {
        $this->prophet->checkPredictions();
    }

    // 测试方法...
}

2. 掌握四大核心概念:Dummy、Stub、Mock与Spy 🧩

Dummy:简单对象占位符

当你只需要满足类型提示而不关心行为时,Dummy是理想选择:

$dummy = $this->prophet->prophesize(stdClass::class)->reveal();

Dummy对象会覆盖所有公共方法并返回null,不包含任何业务逻辑。

Stub:定义有条件的行为

为特定方法调用预设返回值:

$stub = $this->prophet->prophesize(Hasher::class);
$stub->generateHash('password')->willReturn('hashed_password');

支持多种预设行为:

  • willReturn($value):返回固定值
  • willReturnArgument($index):返回指定位置的参数
  • willThrow($exception):抛出异常
  • will($callback):自定义逻辑处理

Mock:验证方法调用预期

预测方法调用次数和参数:

$mock = $this->prophet->prophesize(EntityManager::class);
$mock->flush()->shouldBeCalledTimes(1);

常用预测方法:

  • shouldBeCalled():至少调用一次
  • shouldNotBeCalled():不被调用
  • shouldBeCalledTimes($count):精确调用次数

Spy:事后验证调用行为

先执行代码,再验证方法调用:

$spy = $this->prophet->prophesize(Logger::class)->reveal();
$service = new UserService($spy);
$service->createUser('test@example.com');

$spy->log('user_created', Argument::type('array'))->shouldHaveBeenCalled();

3. 参数匹配:让测试更灵活的7种技巧 🔍

Prophecy提供强大的参数匹配能力,让你无需精确指定每个参数:

基础匹配器

use Prophecy\Argument;

// 精确匹配
$user->setName(Argument::exact('John'));

// 类型匹配
$user->setAge(Argument::type('int'));

// 任意值匹配
$user->log(Argument::any());

高级匹配技巧

// 字符串包含
Argument::containingString('error');

// 对象状态检查
Argument::which('isValid', true);

// 数组包含
Argument::in(['active', 'pending']);

// 回调验证
Argument::that(function ($value) {
    return strlen($value) > 5;
});

参数通配符组合

$order->calculateTotal(
    Argument::type('float'),
    Argument::any(),
    Argument::cetera()
)->willReturn(99.99);

4. 方法预言:构建复杂对象交互 🔄

方法调用链

$user->getName()->willReturn(null);
$user->setName(Argument::type('string'))->will(function ($args) {
    $this->getName()->willReturn($args[0]);
});

多返回值序列

// 第一次返回1,第二次返回2,之后返回3
$counter->increment()->willReturn(1, 2, 3);

方法预言幂等性

Prophecy确保相同参数的方法预言始终返回同一实例:

$method1 = $prophecy->read('id');
$method2 = $prophecy->read('id');
var_dump($method1 === $method2); // true

5. 异常测试:验证错误处理逻辑 ⚠️

基础异常测试

$file->open('invalid.txt')->willThrow(new FileNotFoundException());

$this->expectException(FileNotFoundException::class);
$file->open('invalid.txt');

异常消息验证

$service->process(Argument::type('array'))
    ->willThrow(new InvalidArgumentException('Invalid data format'));

$this->expectExceptionMessage('Invalid data format');

6. 最佳实践:编写可维护的Prophecy测试 ✅

保持测试专注

每个测试只验证一个行为:

// 👍 推荐:单一职责
public function testUserPasswordIsHashedOnCreation() { ... }

// 👎 不推荐:混合多个断言
public function testUserCreationAndUpdateAndDelete() { ... }

使用描述性命名

// 👍 推荐:清晰表达测试意图
public function testLoginFailsWhenPasswordIsIncorrect() { ... }

// 👎 不推荐:模糊不清
public function testLogin() { ... }

避免过度指定

只验证必要的交互,不要测试实现细节:

// 👍 推荐:关注结果而非过程
$this->assertEquals('hashed', $user->getPassword());

// 👎 不推荐:过度指定内部调用
$hasher->hash(Argument::any())->shouldBeCalled()->willReturn('hashed');

7. 与PHPUnit集成:无缝测试体验 🤝

基础集成模板

use PHPUnit\Framework\TestCase;
use Prophecy\Prophet;

class ProductServiceTest extends TestCase
{
    private $prophet;

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

    protected function tearDown(): void
    {
        $this->prophet->checkPredictions();
    }

    public function testCalculatePrice()
    {
        $repository = $this->prophet->prophesize(ProductRepository::class);
        $repository->find(1)->willReturn(new Product(100));
        
        $service = new ProductService($repository->reveal());
        $this->assertEquals(100, $service->calculatePrice(1));
    }
}

数据提供者支持

/**
 * @dataProvider priceProvider
 */
public function testCalculatePriceWithTax($price, $tax, $expected)
{
    $calculator = $this->prophet->prophesize(TaxCalculator::class);
    $calculator->calculate($price)->willReturn($price * $tax);
    
    $service = new PricingService($calculator->reveal());
    $this->assertEquals($expected, $service->calculateWithTax($price));
}

public function priceProvider()
{
    return [
        [100, 1.2, 120],
        [50, 1.2, 60],
    ];
}

结语:提升PHP测试效率的终极工具 🚀

Prophecy通过简洁的API和强大的功能,彻底改变了PHP单元测试的编写方式。无论是创建简单的存根对象,还是构建复杂的方法交互预言,Prophecy都能让测试代码更加清晰、可读且易于维护。

通过掌握本文介绍的7个最佳实践,你将能够充分利用Prophecy的潜力,编写出更健壮、更可靠的PHP单元测试,从而提高代码质量并加速开发流程。

要开始使用Prophecy,只需通过以下命令克隆仓库:

git clone https://gitcode.com/gh_mirrors/pr/prophecy

立即尝试Prophecy,体验PHP单元测试的全新方式!

【免费下载链接】prophecy Highly opinionated mocking framework for PHP 5.3+ 【免费下载链接】prophecy 项目地址: https://gitcode.com/gh_mirrors/pr/prophecy

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐