如何为无函数与类的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
相关产品推荐
相关产品推荐

