ThinkPHP项目模拟请求与断言

wen PHP项目 3

本文目录导读:

ThinkPHP项目模拟请求与断言

  1. 使用PHPUnit进行接口测试
  2. 创建具体的测试类
  3. 使用ThinkPHP内置测试组件
  4. 高级断言技巧
  5. 使用测试辅助工具
  6. 配置文件

我来详细介绍ThinkPHP项目中如何进行模拟请求和断言测试。

使用PHPUnit进行接口测试

1 安装依赖

composer require phpunit/phpunit --dev
composer require guzzlehttp/guzzle --dev

2 创建测试基类

<?php
// tests/TestCase.php
namespace tests;
use PHPUnit\Framework\TestCase as BaseTestCase;
abstract class TestCase extends BaseTestCase
{
    protected $app;
    protected $httpClient;
    protected function setUp(): void
    {
        parent::setUp();
        // 初始化ThinkPHP应用
        $this->app = new \think\App();
        $this->app->initialize();
        // 创建Guzzle客户端
        $this->httpClient = new \GuzzleHttp\Client([
            'base_uri' => 'http://localhost:8080',
            'timeout' => 5.0,
            'http_errors' => false
        ]);
    }
    /**
     * 发送GET请求
     */
    protected function get($uri, $headers = [])
    {
        try {
            $response = $this->httpClient->get($uri, [
                'headers' => array_merge([
                    'Accept' => 'application/json',
                ], $headers)
            ]);
            return $this->parseResponse($response);
        } catch (\Exception $e) {
            return [
                'status' => 500,
                'body' => ['message' => $e->getMessage()],
                'raw' => $e
            ];
        }
    }
    /**
     * 发送POST请求
     */
    protected function post($uri, $data = [], $headers = [])
    {
        try {
            $response = $this->httpClient->post($uri, [
                'json' => $data,
                'headers' => array_merge([
                    'Accept' => 'application/json',
                ], $headers)
            ]);
            return $this->parseResponse($response);
        } catch (\Exception $e) {
            return [
                'status' => 500,
                'body' => ['message' => $e->getMessage()],
                'raw' => $e
            ];
        }
    }
    /**
     * 解析响应
     */
    protected function parseResponse($response)
    {
        $statusCode = $response->getStatusCode();
        $content = $response->getBody()->getContents();
        $json = json_decode($content, true);
        return [
            'status' => $statusCode,
            'body' => $json !== null ? $json : ['raw' => $content],
            'headers' => $response->getHeaders()
        ];
    }
    /**
     * 断言响应状态码
     */
    protected function assertStatus($response, $expectedStatus)
    {
        $this->assertEquals($expectedStatus, $response['status'], 
            "Expected status code {$expectedStatus}, got {$response['status']}");
    }
    /**
     * 断言响应包含指定字段
     */
    protected function assertResponseHasKey($response, $key)
    {
        $this->assertArrayHasKey($key, $response['body'], 
            "Response does not contain key '{$key}'");
    }
    /**
     * 断言响应等于预期值
     */
    protected function assertResponseEquals($response, $expected, $key = null)
    {
        if ($key !== null) {
            $this->assertEquals($expected, $response['body'][$key] ?? null);
        } else {
            $this->assertEquals($expected, $response['body']);
        }
    }
}

创建具体的测试类

1 用户接口测试

<?php
// tests/Api/UserTest.php
namespace tests\Api;
use tests\TestCase;
class UserTest extends TestCase
{
    protected $token;
    /**
     * 测试用户登录
     * @test
     */
    public function testLogin()
    {
        $response = $this->post('/api/user/login', [
            'username' => 'admin',
            'password' => '123456'
        ]);
        $this->assertStatus($response, 200);
        $this->assertResponseHasKey($response, 'token');
        $this->assertResponseHasKey($response, 'user_info');
        $this->token = $response['body']['token'];
        return $response['body']['token'];
    }
    /**
     * 测试获取用户信息
     * @test
     * @depends testLogin
     */
    public function testGetUserInfo($token)
    {
        $response = $this->get('/api/user/info', [
            'Authorization' => "Bearer {$token}"
        ]);
        $this->assertStatus($response, 200);
        $this->assertResponseEquals($response, 'success', 'message');
        $this->assertResponseHasKey($response, 'data');
    }
    /**
     * 测试更新用户信息
     * @test
     */
    public function testUpdateUser()
    {
        $token = $this->testLogin()['token'] ?? '';
        $response = $this->post('/api/user/update', [
            'nickname' => '测试用户',
            'email' => 'test@example.com'
        ], [
            'Authorization' => "Bearer {$token}"
        ]);
        $this->assertStatus($response, 200);
        $this->assertResponseEquals($response, 1, 'code');
    }
    /**
     * 测试用户列表(带token)
     * @test
     */
    public function testGetUserList()
    {
        $token = $this->testLogin()['token'] ?? '';
        $response = $this->get('/api/user/list?page=1&limit=10', [
            'Authorization' => "Bearer {$token}"
        ]);
        $this->assertStatus($response, 200);
        $this->assertResponseHasKey($response, 'list');
        $this->assertResponseHasKey($response, 'total');
    }
}

2 订单接口测试

<?php
// tests/Api/OrderTest.php
namespace tests\Api;
use tests\TestCase;
class OrderTest extends TestCase
{
    protected $token;
    public function setUp(): void
    {
        parent::setUp();
        // 获取token
        $loginResponse = $this->post('/api/user/login', [
            'username' => 'admin',
            'password' => '123456'
        ]);
        $this->token = $loginResponse['body']['token'] ?? '';
    }
    /**
     * 创建订单
     * @test
     */
    public function testCreateOrder()
    {
        $orderData = [
            'order_no' => 'ORD' . time(),
            'total_amount' => 99.50,
            'items' => [
                ['product_id' => 1, 'quantity' => 2, 'price' => 49.75]
            ]
        ];
        $response = $this->post('/api/order/create', $orderData, [
            'Authorization' => "Bearer {$this->token}"
        ]);
        $this->assertStatus($response, 200);
        $this->assertResponseHasKey($response, 'order_id');
    }
    /**
     * 查询订单详情
     * @test
     */
    public function testGetOrderInfo()
    {
        $orderId = 1;
        $response = $this->get("/api/order/info/{$orderId}", [
            'Authorization' => "Bearer {$this->token}"
        ]);
        $this->assertStatus($response, 200);
        $this->assertResponseHasKey($response, 'data');
        $this->assertArrayHasKey('order_no', $response['body']['data'] ?? []);
    }
    /**
     * 批量断言测试
     * @test
     */
    public function testApiResponseStructure()
    {
        $response = $this->get('/api/user/list', [
            'Authorization' => "Bearer {$this->token}"
        ]);
        // 多字段断言
        $expectedKeys = ['code', 'message', 'data'];
        foreach ($expectedKeys as $key) {
            $this->assertArrayHasKey($key, $response['body'], "Missing key: {$key}");
        }
        // 类型断言
        $this->assertIsInt($response['body']['code'] ?? null);
        $this->assertIsString($response['body']['message'] ?? null);
        $this->assertIsArray($response['body']['data'] ?? null);
        // 范围断言
        $this->assertGreaterThan(-1, $response['body']['code'] ?? -1);
        $this->assertLessThan(1000, $response['body']['code'] ?? 999);
    }
}

使用ThinkPHP内置测试组件

1 创建测试基类(使用ThinkPHP的测试支持)

<?php
// tests/BaseTest.php
namespace tests;
use think\App;
use think\Container;
use PHPUnit\Framework\TestCase;
class BaseTest extends TestCase
{
    /** @var App */
    protected $app;
    /** @var Container */
    protected $container;
    /** @var \think\Request */
    protected $request;
    protected function setUp(): void
    {
        parent::setUp();
        $this->app = new App();
        $this->container = Container::getInstance();
        $this->request = $this->app->request;
    }
    /**
     * 模拟请求
     */
    protected function request($method, $url, $data = [], $headers = [])
    {
        // 构造请求对象
        $this->request->setMethod($method);
        $this->request->url($url);
        // 设置请求数据
        $this->request->withInput($data);
        // 设置请求头
        foreach ($headers as $key => $value) {
            $this->request->header([$key => $value]);
        }
        // 执行路由
        $response = $this->app->http->run($this->request);
        return $response;
    }
    /**
     * 模拟GET请求
     */
    protected function get($url, $params = [], $headers = [])
    {
        $url = $url . (strpos($url, '?') ? '&' : '?') . http_build_query($params);
        return $this->request('GET', $url, [], $headers);
    }
    /**
     * 模拟POST请求
     */
    protected function post($url, $data = [], $headers = [])
    {
        return $this->request('POST', $url, $data, $headers);
    }
    /**
     * 模拟PUT请求
     */
    protected function put($url, $data = [], $headers = [])
    {
        return $this->request('PUT', $url, $data, $headers);
    }
    /**
     * 模拟DELETE请求
     */
    protected function delete($url, $headers = [])
    {
        return $this->request('DELETE', $url, [], $headers);
    }
    /**
     * 从响应中提取数据
     */
    protected function responseData($response)
    {
        $content = $response->getContent();
        return json_decode($content, true);
    }
}

2 使用ThinkPHP控制器测试

<?php
// tests/Controller/UserControllerTest.php
namespace tests\Controller;
use tests\BaseTest;
use think\facade\Route;
class UserControllerTest extends BaseTest
{
    /**
     * 测试用户登录控制器
     */
    public function testLoginAction()
    {
        // 定义路由
        Route::post('/api/login', 'UserController@login');
        $data = [
            'username' => 'admin',
            'password' => '123456'
        ];
        $response = $this->post('/api/login', $data);
        $result = $this->responseData($response);
        $this->assertEquals(200, $response->getCode());
        $this->assertTrue($result['success']);
        $this->assertArrayHasKey('token', $result['data']);
    }
    /**
     * 测试用户列表控制器
     */
    public function testListAction()
    {
        $response = $this->get('/api/users', ['page' => 1]);
        $result = $this->responseData($response);
        $this->assertEquals(200, $response->getCode());
        $this->assertArrayHasKey('users', $result['data']);
        $this->assertIsArray($result['data']['users']);
    }
}

高级断言技巧

1 数据校验断言

<?php
// tests/Traits/AssertionHelpers.php
namespace tests\Traits;
trait AssertionHelpers
{
    /**
     * 断言数据为空或null
     */
    public function assertEmptyOrNull($value, $message = '')
    {
        $this->assertTrue(empty($value), $message);
    }
    /**
     * 断言数据格式正确(邮箱、手机号等)
     */
    public function assertValidEmail($email, $message = '')
    {
        $this->assertMatchesRegularExpression(
            '/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/',
            $email,
            $message
        );
    }
    /**
     * 断言时间格式
     */
    public function assertValidDateTime($datetime, $message = '')
    {
        $this->assertMatchesRegularExpression(
            '/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/',
            $datetime,
            $message
        );
    }
    /**
     * 断言数组包含指定键
     */
    public function assertArrayContainsKeys($array, $keys, $message = '')
    {
        foreach ($keys as $key) {
            $this->assertArrayHasKey($key, $array, 
                $message ?: "Array should contain key '{$key}'");
        }
    }
    /**
     * 断言响应成功状态
     */
    public function assertResponseSuccess($response, $message = '')
    {
        $this->assertEquals(200, $response['status'] ?? null, $message);
        $this->assertEquals(1, $response['body']['code'] ?? null, $message);
        $this->assertEquals('success', $response['body']['message'] ?? null, $message);
    }
}

2 断言链式测试

<?php
// tests/ChainedTest.php
namespace tests;
use tests\Traits\AssertionHelpers;
class ChainedTest extends TestCase
{
    use AssertionHelpers;
    /**
     * 链式测试示例
     * @test
     */
    public function testChainedAssertions()
    {
        // 模拟响应数据
        $response = [
            'status' => 200,
            'body' => [
                'code' => 1,
                'message' => 'success',
                'data' => [
                    'user' => [
                        'id' => 1,
                        'username' => 'admin',
                        'email' => 'admin@example.com',
                        'created_at' => '2023-01-01 10:00:00'
                    ],
                    'orders' => [
                        ['id' => 1, 'total' => 100],
                        ['id' => 2, 'total' => 200]
                    ]
                ]
            ]
        ];
        // 链式断言
        $this->assertResponseSuccess($response)
            ->assertArrayContainsKeys($response['body']['data'], ['user', 'orders'])
            ->assertValidEmail($response['body']['data']['user']['email'])
            ->assertValidDateTime($response['body']['data']['user']['created_at'])
            ->assertCount(2, $response['body']['data']['orders'])
            ->assertGreaterThan(0, $response['body']['data']['orders'][0]['total']);
    }
}

使用测试辅助工具

1 创建测试工具类

<?php
// tests/Helpers/TestHelper.php
namespace tests\Helpers;
class TestHelper
{
    /**
     * 生成测试数据
     */
    public static function generateTestData($type = 'user')
    {
        switch ($type) {
            case 'user':
                return [
                    'username' => 'test_' . time(),
                    'password' => '123456',
                    'email' => 'test_' . time() . '@example.com'
                ];
            case 'order':
                return [
                    'order_no' => 'ORD' . time(),
                    'amount' => rand(100, 9999) / 100
                ];
            default:
                return [];
        }
    }
    /**
     * 基础认证头
     */
    public static function authHeader($token)
    {
        return [
            'Authorization' => "Bearer {$token}",
            'Accept' => 'application/json',
            'Content-Type' => 'application/json'
        ];
    }
}

2 使用测试工具类

<?php
// tests/Api/IntegrationTest.php
namespace tests\Api;
use tests\TestCase;
use tests\Helpers\TestHelper;
class IntegrationTest extends TestCase
{
    /**
     * 完整的业务流测试
     * @test
     */
    public function testCompleteBusinessFlow()
    {
        // 1. 用户注册
        $userData = TestHelper::generateTestData('user');
        $registerResponse = $this->post('/api/register', $userData);
        $this->assertStatus($registerResponse, 200);
        // 2. 用户登录
        $loginResponse = $this->post('/api/login', [
            'username' => $userData['username'],
            'password' => $userData['password']
        ]);
        $this->assertStatus($loginResponse, 200);
        $token = $loginResponse['body']['token'];
        // 3. 创建订单
        $orderData = TestHelper::generateTestData('order');
        $orderResponse = $this->post('/api/orders', $orderData, 
            TestHelper::authHeader($token));
        $this->assertStatus($orderResponse, 200);
        $orderId = $orderResponse['body']['order_id'];
        // 4. 查询订单
        $getOrderResponse = $this->get("/api/orders/{$orderId}", 
            TestHelper::authHeader($token));
        $this->assertStatus($getOrderResponse, 200);
        // 5. 删除订单
        $deleteResponse = $this->delete("/api/orders/{$orderId}", 
            TestHelper::authHeader($token));
        $this->assertStatus($deleteResponse, 200);
    }
}

配置文件

<?php
// phpunit.xml
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false">
    <testsuites>
        <testsuite name="Application Test Suite">
            <directory>./tests</directory>
        </testsuite>
    </testsuites>
    <php>
        <env name="APP_ENV" value="testing"/>
        <env name="DB_HOST" value="127.0.0.1"/>
        <env name="DB_NAME" value="test_database"/>
        <env name="DB_USER" value="root"/>
        <env name="DB_PASS" value=""/>
        <env name="DB_PORT" value="3306"/>
    </php>
</phpunit>

这个测试框架提供了完整的模拟请求和断言功能,可以根据项目需求灵活使用,建议在开发过程中持续编写和维护测试,确保代码质量和功能稳定性。

抱歉,评论功能暂时关闭!