You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为无函数与类的PHP脚本编写单元测试?

如何为纯PHP跳转脚本编写单元测试

问题场景

需要为一段无函数/类的纯PHP脚本编写单元测试,该脚本会根据用户权限条件执行header('Location: ...')跳转,但现有测试用例无法捕获到预期的Location响应头。

目标脚本代码

<?php
error_reporting(0);
include XOOPS_ROOT_PATH . "/include/curl/functions.php";
include "ADBConnection.php";
include "MDBConnection.php";

global $user;
if (!empty($user) && is_object($user)) {

    $a_connection = new ADBConnection();
    $m_conn = new MDBConnection();
    $conn = $a_connection->getConnection();
    $a_connection->setSqlQuery($some_sql);

    // 注意:原脚本存在变量错误,$db_connection未定义,应为$a_connection
    $prepare = $conn->prepare($a_connection->getSqlQuery());
    $prepare->execute([
        "user_id" => $user->customer_id(),
    ]);
    $result = $prepare->fetchAll();

    $is_allow = false;
    if (!empty($result)) {
        foreach ($result as $item) {
            if ($item['is_allow'] > 0) {
                $is_allow = true;
                break;
            }
        }
    }

    $user_admin = $m_conn->getUserAdmin($user->uid());
    if ($is_allow || $user_admin === true) {
        header("Location: ${some_url}");
    } else {
        header("Location: ${another_some_url}");
    }
} else {
    header("Location: /");
}

现有测试用例

protected function setUp()
{
    parent::setUp();

    $this->userMock = $this->getMockBuilder(User::class)
        ->setMethods(['customer_id', 'uid'])
        ->getMock();

    $this->axDatabaseMock = $this->getMockBuilder(ADBConnection::class)
        ->setMethods()
        ->getMock();

    $this->marcDatabaseMock = $this->getMockBuilder(MDBConnection::class)
        ->setMethods(['getUserAdmin'])
        ->getMock();
}

public function test_redirect_user_to_some_url_page()
{

    $this->userMock->method('customer_id')
        ->willReturn(1);
    $this->userMock->method('uid')
        ->willReturn(1);

    $this->marcDatabaseMock->method('getUserAdmin')
        ->willReturn(true);

    ob_start();
    require('path_to_script');
    $headers = xdebug_get_headers();
    $this->assertContains('Location: ${some_url}', $headers);
}

问题现象

调试时$headers仅返回Content-Type头,无预期的Location头:

array(1) {
  [0] =>
  string(38) "Content-type: text/html; charset=UTF-8"
}

解决方案

1. 修复脚本自身的错误

原脚本存在**未定义变量$db_connection**的致命错误,被error_reporting(0)掩盖,导致跳转逻辑根本无法执行。需将$conn->prepare($db_connection->getSqlQuery())修正为$conn->prepare($a_connection->getSqlQuery())。

2. 正确传递全局变量与Mock实例

脚本依赖全局变量和直接实例化的类,测试时需手动注入这些依赖:

  • 为脚本中用到的全局变量($user、$some_sql、$some_url、$another_some_url、$XOOPS_ROOT_PATH)赋值
  • 使用class_alias临时替换实际类,让脚本中new ADBConnection()和new MDBConnection()返回Mock实例

3. 完善Mock方法覆盖

需为脚本中调用的类方法创建Mock逻辑,包括数据库连接、查询执行、权限判断等步骤:

  • Mock ADBConnection的getConnection、setSqlQuery、getSqlQuery方法
  • Mock PDO和PDOStatement的prepare、execute、fetchAll方法,模拟数据库查询结果
  • Mock MDBConnection的getUserAdmin方法返回预期值

4. 准确捕获HTTP头信息

  • 用headers_list()替代xdebug_get_headers(),兼容性更好
  • 用ob_end_clean()清空输出缓冲,避免无关输出干扰头信息捕获

修正后的完整测试用例

protected function setUp(): void
{
    parent::setUp();

    // 初始化脚本依赖的全局变量
    global $XOOPS_ROOT_PATH, $some_sql, $some_url, $another_some_url;
    $XOOPS_ROOT_PATH = __DIR__; // 根据实际项目路径调整
    $some_sql = 'SELECT is_allow FROM user_permissions WHERE user_id = :user_id';
    $some_url = 'https://example.com/allowed';
    $another_some_url = 'https://example.com/denied';

    // Mock User类
    $this->userMock = $this->getMockBuilder(User::class)
        ->setMethods(['customer_id', 'uid'])
        ->getMock();

    // Mock ADBConnection并替换实际类
    $this->axDatabaseMock = $this->getMockBuilder(ADBConnection::class)
        ->setMethods(['getConnection', 'setSqlQuery', 'getSqlQuery'])
        ->getMock();
    class_alias(get_class($this->axDatabaseMock), 'ADBConnection');

    // Mock PDOStatement模拟查询执行
    $stmtMock = $this->getMockBuilder(PDOStatement::class)
        ->setMethods(['execute', 'fetchAll'])
        ->getMock();
    $stmtMock->method('execute')->willReturn(true);
    $stmtMock->method('fetchAll')->willReturn([]); // 模拟无权限记录的情况

    // Mock PDO连接
    $pdoMock = $this->getMockBuilder(PDO::class)
        ->setMethods(['prepare'])
        ->getMock();
    $pdoMock->method('prepare')->with($some_sql)->willReturn($stmtMock);

    // 绑定ADBConnection的Mock方法
    $this->axDatabaseMock->method('getConnection')->willReturn($pdoMock);
    $this->axDatabaseMock->method('getSqlQuery')->willReturn($some_sql);
    $this->axDatabaseMock->method('setSqlQuery')->with($some_sql)->willReturnSelf();

    // Mock MDBConnection并替换实际类
    $this->marcDatabaseMock = $this->getMockBuilder(MDBConnection::class)
        ->setMethods(['getUserAdmin'])
        ->getMock();
    class_alias(get_class($this->marcDatabaseMock), 'MDBConnection');
}

public function test_redirect_user_to_some_url_page(): void
{
    global $user;
    $user = $this->userMock;

    // 设置User Mock的返回值
    $this->userMock->method('customer_id')->willReturn(1);
    $this->userMock->method('uid')->willReturn(1);

    // 设置用户为管理员
    $this->marcDatabaseMock->method('getUserAdmin')->with(1)->willReturn(true);

    // 执行脚本并捕获头信息
    ob_start();
    require __DIR__ . '/path_to_script.php';
    ob_end_clean();

    $headers = headers_list();
    $this->assertContains("Location: {$some_url}", $headers);
}

内容的提问来源于stack exchange,提问作者Visal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 03:11:05