本文目录导读:

我来详细介绍如何在PHP中进行Pact契约测试。
Pact基础概念
Pact是一种契约测试工具,用于确保服务提供者和消费者之间的API兼容性,主要有两个角色:
- Consumer(消费者):调用API的一方
- Provider(提供者):提供API的一方
环境准备
安装依赖
# 消费者端 composer require pact-foundation/pact-php # 提供者端 composer require --dev pact-foundation/pact-php
消费者端测试(Consumer Testing)
创建消费者测试
<?php
// tests/Consumer/ConsumerTest.php
use PHPUnit\Framework\TestCase;
use PhpPact\Consumer\InteractionBuilder;
use PhpPact\Consumer\Matcher\Matcher;
use PhpPact\Consumer\Model\ConsumerRequest;
use PhpPact\Consumer\Model\ProviderResponse;
use PhpPact\Standalone\MockService\MockServerConfig;
class UserServiceConsumerTest extends TestCase
{
private $httpClient;
private $mockServer;
protected function setUp(): void
{
// 配置Mock服务
$config = new MockServerConfig();
$config->setHost('localhost');
$config->setPort(7200);
$config->setConsumer('UserServiceConsumer');
$config->setProvider('UserApiProvider');
$config->setPactDir(__DIR__ . '/pacts');
// 创建交互构建器
$builder = new InteractionBuilder($config);
$this->mockServer = $config;
// 配置HTTP客户端
$this->httpClient = new \GuzzleHttp\Client([
'base_uri' => 'http://localhost:7200'
]);
}
/** @test */
public function testGetUserById()
{
$matcher = new Matcher();
// 定义消费者请求
$request = new ConsumerRequest();
$request
->setMethod('GET')
->setPath('/api/users/1')
->addHeader('Accept', 'application/json');
// 定义提供者响应
$response = new ProviderResponse();
$response
->setStatus(200)
->addHeader('Content-Type', 'application/json')
->setBody([
'id' => $matcher->integerType(1),
'name' => $matcher->stringType('John Doe'),
'email' => $matcher->stringType('john@example.com'),
'age' => $matcher->integerType(30)
]);
// 注册交互
$interaction = new \PhpPact\Consumer\Model\Interaction();
$interaction
->setDescription('Get user by ID')
->setProviderState('user with ID 1 exists')
->setRequest($request)
->setResponse($response);
$builder->addInteraction($interaction);
// 发送实际请求
$result = $this->httpClient->get('/api/users/1', [
'headers' => ['Accept' => 'application/json']
]);
// 验证响应
$this->assertEquals(200, $result->getStatusCode());
$data = json_decode($result->getBody(), true);
$this->assertEquals(1, $data['id']);
$this->assertNotEmpty($data['name']);
$this->assertTrue(filter_var($data['email'], FILTER_VALIDATE_EMAIL) !== false);
// 完成后会生成pact文件
}
}
使用Matcher更多功能
<?php
// tests/Consumer/AdvancedConsumerTest.php
class AdvancedConsumerTest extends TestCase
{
/** @test */
public function testComplexApiInteractions()
{
$matcher = new Matcher();
// 请求体匹配器
$request = new ConsumerRequest();
$request
->setMethod('POST')
->setPath('/api/users')
->addHeader('Content-Type', 'application/json')
->setBody([
'name' => $matcher->stringType('Test User'),
'email' => $matcher->stringType('test@example.com'),
'roles' => $matcher->arrayContaining(['admin']),
'metadata' => $matcher->eachLike([
'key' => $matcher->stringType('test'),
'value' => $matcher->stringType('value')
])
]);
// 响应匹配器
$response = new ProviderResponse();
$response
->setStatus(201)
->addHeader('Content-Type', 'application/json')
->setBody([
'id' => $matcher->integerType(1),
'name' => $matcher->like('Test User'),
'email' => $matcher->email('test@example.com'),
'created_at' => $matcher->dateTime(),
'links' => $matcher->eachLike([
'rel' => $matcher->stringType('self'),
'href' => $matcher->url('http://example.com/api/users/1'),
])
]);
// 更多的匹配器使用...
}
}
提供者端测试(Provider Testing)
创建提供者测试
<?php
// tests/Provider/ProviderTest.php
use PHPUnit\Framework\TestCase;
use PhpPact\Standalone\Provider\ProviderVerifier;
use PhpPact\Standalone\Provider\ProviderVerifierOptions;
class UserApiProviderTest extends TestCase
{
/** @test */
public function testUserApiProviderContract()
{
// 配置提供者验证器
$options = new ProviderVerifierOptions();
$options->setProviderName('UserApiProvider');
$options->setProviderBaseUrl('http://localhost:8080');
$options->setPactBrokerUri('http://localhost:9292'); // Pact Broker地址
$options->setPublishResults(true);
$options->setBrokerToken('your-broker-token');
// 创建验证器
$verifier = new ProviderVerifier($options);
// 运行验证
try {
$verifier->verify();
$this->assertTrue(true);
} catch (\Exception $e) {
$this->fail('契约验证失败: ' . $e->getMessage());
}
}
}
使用本地pact文件验证
<?php
// tests/Provider/LocalPactProviderTest.php
class LocalPactProviderTest extends TestCase
{
/** @test */
public function testWithLocalPactFiles()
{
$options = new ProviderVerifierOptions();
$options->setProviderName('UserApiProvider');
$options->setProviderBaseUrl('http://localhost:8080');
$options->setPactFiles([
__DIR__ . '/pacts/UserServiceConsumer-UserApiProvider.json'
]);
$verifier = new ProviderVerifier($options);
// 验证所有交互
$verifier->verify();
}
}
使用Pact Broker
配置Pact Broker
<?php
// config/pact_broker.php
return [
'broker_url' => 'http://localhost:9292',
'consumer_version' => '1.0.0',
'provider_version' => '1.0.0',
'publish_results' => true,
'broker_token' => null, // 如果需要认证
];
发布和管理契约
# 启动Pact Broker (使用Docker) docker run -d -p 9292:9292 pactfoundation/pact-broker # 查看Broker UI # http://localhost:9292
集成到CI/CD
Jenkins配置
pipeline {
agent any
stages {
stage('Consumer Tests') {
steps {
sh 'composer install'
sh 'php vendor/bin/phpunit --testsuite Consumer'
// 发布pact文件到Broker
sh 'curl -X PUT \
-H "Content-Type: application/json" \
-d @pacts/UserServiceConsumer-UserApiProvider.json \
http://localhost:9292/pacts/provider/UserApiProvider/consumer/UserServiceConsumer/version/1.0.0'
}
}
stage('Provider Tests') {
steps {
// 启动服务
sh 'php -S localhost:8080 -t public/ &'
// 运行提供者验证
sh 'php vendor/bin/phpunit --testsuite Provider'
}
}
}
}
完整的示例应用
消费者客户端
<?php
// src/UserServiceClient.php
class UserServiceClient
{
private $httpClient;
public function __construct($baseUrl)
{
$this->httpClient = new \GuzzleHttp\Client([
'base_uri' => $baseUrl
]);
}
public function getUser($id)
{
$response = $this->httpClient->get("/api/users/{$id}");
return json_decode($response->getBody(), true);
}
public function createUser($userData)
{
$response = $this->httpClient->post('/api/users', [
'json' => $userData
]);
return json_decode($response->getBody(), true);
}
}
提供者API
<?php
// public/users.php
class UserApiController
{
public function getUser($id)
{
// 数据库查询
$user = [
'id' => (int)$id,
'name' => 'John Doe',
'email' => 'john@example.com',
'age' => 30
];
return json_encode($user);
}
public function createUser()
{
$data = json_decode(file_get_contents('php://input'), true);
// 创建用户
return json_encode([
'id' => 1,
'name' => $data['name'],
'email' => $data['email']
], JSON_PRETTY_PRINT);
}
}
最佳实践
契约测试注意事项
// 1. 保持匹配器合理使用
class ContractTestBestPractices
{
public function testContractExamples()
{
$matcher = new Matcher();
// ✅ 好:使用合理范围
$response->setBody([
'id' => $matcher->integerType(100),
'name' => $matcher->stringType('Valid Name')
]);
// ❌ 避免:过度精确
// 'id' => 100 // 不要硬编码具体值
// ✅ 好:使用灵活匹配
'email' => $matcher->stringType('user@example.com')
// ❌ 避免:使用正则过于严格
// $matcher->regex('\\d{5}', '12345') // 可能过于限制
}
// 2. 处理可选字段
public function testOptionalFields()
{
$matcher = new Matcher();
$response->setBody([
'id' => $matcher->integerType(1),
'name' => $matcher->stringType('Test'),
'profile' => $matcher->eachLike([
'avatar' => $matcher->stringType('url')
]),
'contact' => $matcher->like([
'phone' => $matcher->stringType('123-456-7890')
])
]);
}
}
测试组织
// phpunit.xml
<phpunit>
<testsuites>
<testsuite name="Consumer">
<directory>tests/Consumer</directory>
</testsuite>
<testsuite name="Provider">
<directory>tests/Provider</directory>
</testsuite>
</testsuites>
</phpunit>
常见问题解决
// 处理回调/状态变化
class StateHandlerTest extends TestCase
{
public function testWithStateHandlers()
{
// 定义provider states
$stateHandler = function ($state) {
switch ($state) {
case 'user with ID 1 exists':
// 准备测试数据
$this->seedDatabase([...]);
break;
case 'user with ID 999 does not exist':
// 删除测试用户
break;
}
};
// 传递给验证器
$verifier = new ProviderVerifier($options);
$verifier->setStateHandler($stateHandler);
$verifier->verify();
}
}
通过遵循这些实践,你就能在PHP项目中有效地使用Pact进行契约测试了。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播