本文目录导读:

我来详细介绍PHP项目的Codeception全栈测试实现方案。
Codeception安装与配置
安装Codeception
# 通过Composer安装 composer require --dev codeception/codeception # 初始化(选择项目类型) vendor/bin/codecept init
项目结构
tests/
├── acceptance/ # 接受测试
├── functional/ # 功能测试
├── unit/ # 单元测试
├── _support/ # 辅助类
│ ├── AcceptanceTester.php
│ ├── FunctionalTester.php
│ └── UnitTester.php
├── _output/ # 测试输出
├── _data/ # 测试数据
└── codeception.yml # 主配置文件
配置文件示例
codeception.yml
namespace: Tests
paths:
tests: tests
output: tests/_output
data: tests/_data
support: tests/_support
envs: tests/_envs
actor_suffix: Tester
extensions:
enabled:
- Codeception\Extension\RunFailed
# 数据库配置
params:
- .env
# 模块配置
modules:
config:
Db:
dsn: '%DB_DSN%'
user: '%DB_USER%'
password: '%DB_PASSWORD%'
dump: tests/_data/dump.sql
populate: true
cleanup: true
reconnect: true
测试类型详解
单元测试 (Unit Tests)
<?php
// tests/unit/UserServiceTest.php
class UserServiceTest extends \Codeception\Test\Unit
{
/**
* @var \UnitTester
*/
protected $tester;
private $userService;
protected function _before()
{
// 模拟Repository
$userRepository = $this->createMock(UserRepositoryInterface::class);
$this->userService = new UserService($userRepository);
}
public function testCreateUser()
{
$userData = [
'name' => 'John Doe',
'email' => 'john@example.com',
'password' => 'secure123'
];
$result = $this->userService->create($userData);
$this->assertInstanceOf(User::class, $result);
$this->assertEquals($userData['name'], $result->getName());
}
public function testFindUserById()
{
$userId = 1;
$this->tester->haveRecord('users', [
'id' => $userId,
'name' => 'John Doe',
'email' => 'john@example.com'
]);
$user = $this->userService->findById($userId);
$this->assertNotNull($user);
$this->assertEquals($userId, $user->getId());
}
}
功能测试 (Functional Tests)
<?php
// tests/functional/UserControllerCest.php
class UserControllerCest
{
public function _before(FunctionalTester $I)
{
// 数据库迁移
$I->haveHttpHeader('X-API-Key', 'test-api-key');
// 创建测试用户
$I->haveRecord('users', [
'id' => 1,
'email' => 'test@example.com',
'password' => password_hash('password123', PASSWORD_DEFAULT),
'role' => 'admin'
]);
}
public function testGetUser(FunctionalTester $I)
{
$I->sendGET('/api/users/1');
$I->seeResponseCodeIs(200);
$I->seeResponseIsJson();
$I->seeResponseContainsJson([
'id' => 1,
'email' => 'test@example.com'
]);
}
public function testCreateUser(FunctionalTester $I)
{
$I->sendPOST('/api/users', [
'name' => 'New User',
'email' => 'newuser@example.com',
'password' => 'secure123'
]);
$I->seeResponseCodeIs(201);
$I->seeResponseIsJson();
// 验证数据库
$I->seeInDatabase('users', [
'email' => 'newuser@example.com'
]);
}
public function testInvalidEmail(FunctionalTester $I)
{
$I->sendPOST('/api/users', [
'email' => 'invalid-email',
'password' => 'secure123'
]);
$I->seeResponseCodeIs(422);
$I->seeResponseContains('The email must be a valid email address.');
}
}
接受测试 (Acceptance Tests)
<?php
// tests/acceptance/LoginCest.php
class LoginCest
{
public function _before(AcceptanceTester $I)
{
// 创建测试数据
$I->haveRecord('users', [
'email' => 'user@example.com',
'password' => password_hash('pass123', PASSWORD_DEFAULT),
'name' => 'Test User'
]);
}
public function testSuccessfulLogin(AcceptanceTester $I)
{
$I->amOnPage('/login');
$I->fillField('email', 'user@example.com');
$I->fillField('password', 'pass123');
$I->click('Login');
$I->seeCurrentUrlEquals('/dashboard');
$I->see('Welcome, Test User');
}
public function testFailedLogin(AcceptanceTester $I)
{
$I->amOnPage('/login');
$I->fillField('email', 'wrong@example.com');
$I->fillField('password', 'wrongpassword');
$I->click('Login');
$I->see('Invalid credentials');
$I->seeCurrentUrlEquals('/login');
}
public function testFormValidation(AcceptanceTester $I)
{
$I->amOnPage('/login');
$I->click('Login'); // 空表单提交
$I->see('The email field is required');
$I->see('The password field is required');
}
}
高级测试模式
数据供给器 (Data Providers)
<?php
class MathTest extends \Codeception\Test\Unit
{
/**
* @dataProvider additionProvider
*/
public function testAddition($a, $b, $expected)
{
$result = $a + $b;
$this->assertEquals($expected, $result);
}
public function additionProvider()
{
return [
'positive numbers' => [1, 2, 3],
'negative numbers' => [-1, -2, -3],
'zero values' => [0, 0, 0],
'mixed values' => [5, -3, 2]
];
}
}
依赖注入测试
<?php
class OrderServiceTest extends \Codeception\Test\Unit
{
/**
* @var \UnitTester
*/
protected $tester;
public function testOrderCreationWithDependencies()
{
// 模拟依赖
$paymentGateway = $this->createMock(PaymentGatewayInterface::class);
$notificationService = $this->createMock(NotificationServiceInterface::class);
$paymentGateway->method('process')
->willReturn(new PaymentResponse(true, 'txn_123'));
$notificationService->method('send')
->willReturn(true);
$orderService = new OrderService($paymentGateway, $notificationService);
$order = $orderService->create([
'user_id' => 1,
'items' => ['product_1' => 2],
'total' => 100.00
]);
$this->assertInstanceOf(Order::class, $order);
$this->assertEquals('pending', $order->getStatus());
}
}
API测试
<?php
// tests/api/ProductApiCest.php
class ProductApiCest
{
public function testProductCRUD(ApiTester $I)
{
// 认证
$I->haveHttpHeader('Authorization', 'Bearer ' . $this->getToken());
$I->haveHttpHeader('Content-Type', 'application/json');
$I->haveHttpHeader('Accept', 'application/json');
// CREATE
$I->sendPOST('/api/products', [
'name' => 'Test Product',
'price' => 29.99,
'category' => 'electronics'
]);
$I->seeResponseCodeIs(201);
$I->seeResponseIsJson();
// 获取创建的产品ID
$productId = $I->grabDataFromResponseByJsonPath('$.id')[0];
// READ
$I->sendGET('/api/products/' . $productId);
$I->seeResponseCodeIs(200);
$I->seeResponseMatchesJsonType([
'id' => 'integer:>0',
'name' => 'string',
'price' => 'float'
]);
// UPDATE
$I->sendPUT('/api/products/' . $productId, [
'price' => 19.99
]);
$I->seeResponseCodeIs(200);
$I->seeResponseContainsJson(['price' => 19.99]);
// DELETE
$I->sendDELETE('/api/products/' . $productId);
$I->seeResponseCodeIs(204);
}
}
测试套件配置
环境配置文件
# tests/_envs/staging.yml
modules:
config:
Db:
dsn: 'mysql:host=staging-db;dbname=test'
WebDriver:
url: 'https://staging.example.com'
运行特定环境测试
# 运行特定环境 vendor/bin/codecept run --env staging # 运行多个环境 vendor/bin/codecept run --env staging --env production # 并行运行测试 vendor/bin/codecept run --parallel
自定义模块与Helper
创建自定义Helper
<?php
// tests/_support/Helper/CustomHelper.php
namespace Helper;
class CustomHelper extends \Codeception\Module
{
public function loginAsUser($email, $password)
{
$webDriver = $this->getModule('WebDriver');
$webDriver->amOnPage('/login');
$webDriver->fillField('email', $email);
$webDriver->fillField('password', $password);
$webDriver->click('Login');
return $this;
}
public function seeSuccessMessage($message)
{
$webDriver = $this->getModule('WebDriver');
$webDriver->see($message, '.alert-success');
return $this;
}
public function generateTestUser($overrides = [])
{
$defaults = [
'name' => 'Test User ' . uniqid(),
'email' => 'test_' . uniqid() . '@example.com',
'password' => 'password123'
];
return array_merge($defaults, $overrides);
}
}
使用自定义Helper
<?php
class UserTest extends \Codeception\Test\Unit
{
/**
* @var \UnitTester
*/
protected $tester;
public function testUserRegistration()
{
$userData = $this->tester->generateTestUser([
'role' => 'premium'
]);
// 使用自定义断言
$this->tester->assertValidEmail($userData['email']);
$this->tester->assertStrongPassword($userData['password']);
}
}
持续集成配置
GitHub Actions示例
# .github/workflows/test.yml
name: PHP Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: test_db
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=10s
steps:
- uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.1'
extensions: mbstring, intl, pdo_mysql
coverage: xdebug
- name: Install dependencies
run: composer install --prefer-dist --no-progress
- name: Setup environment
run: |
cp .env.example .env
php artisan key:generate
- name: Run tests
run: |
vendor/bin/codecept build
vendor/bin/codecept run --coverage --coverage-xml
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
file: tests/_output/coverage.xml
最佳实践
测试组织结构
// 使用标签组织测试
/**
* @group api
* @group critical
*/
public function testCriticalApiEndpoint()
{
// 测试代码
}
// 运行特定标签的测试
// vendor/bin/codecept run --group api
// vendor/bin/codecept run --group critical --skip-group slow
测试数据管理
<?php
class TestDataFactory
{
private static $data = [];
public static function createUser($overrides = [])
{
$defaults = [
'name' => 'Test User',
'email' => self::uniqueEmail(),
'password' => 'password123'
];
$userData = array_merge($defaults, $overrides);
self::$data['users'][] = $userData;
return $userData;
}
public static function cleanup()
{
// 清理测试数据
foreach (self::$data as $table => $records) {
// 删除测试数据
}
self::$data = [];
}
private static function uniqueEmail()
{
return 'test_' . uniqid() . '@example.com';
}
}
性能优化技巧
<?php
class PerformanceTest extends \Codeception\Test\Unit
{
// 使用共享fixture
private static $sharedFixtures = [];
public static function setUpBeforeClass()
{
// 只创建一次共享数据
self::$sharedFixtures = createExpensiveFixtures();
}
public function testFeature1()
{
// 使用共享数据
$this->assertNotNull(self::$sharedFixtures['user']);
}
public function testFeature2()
{
// 重用共享数据
$this->assertCount(10, self::$sharedFixtures['items']);
}
}
调试技巧
# 调试模式运行测试 vendor/bin/codecept run --debug # 暂停测试执行 vendor/bin/codecept run --steps # 重建测试套件 vendor/bin/codecept build # 生成测试报告 vendor/bin/codecept run --html --xml
这个全栈测试方案覆盖了Codeception的各种测试类型和最佳实践,能够帮助你在PHP项目中建立完整的测试体系。
版权声明: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直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播