我正在实现一个模块,该模块将提供一个API来处理和管理PHP会话。我正在测试允许用户启动会话、设置 ID、获取 ID、销毁会话等的 Session'Manager
实现。我正在使用 PHPUnit 的@runInSeparateProcess
注释在单独的进程中测试此类中的方法。当我使用此注释时,由于反序列化错误,PHPUnit 会引发异常。当我不使用注释时,测试按预期运行,并在 null 不等于 false 时失败。
这是导致错误的测试。到目前为止,还没有实现细节,接口的所有方法都存在,但不执行任何操作。
class ManagerTest extends PHPUnitTestCase {
/**
* Ensures that if the session has not been started yet the sessionExists
* method returns false.
*
* @runInSeparateProcess
*/
public function testSessionExistsWhenSessionHasNotBeenStarted() {
$Manager = new 'Session'Manager();
$this->assertFalse($Manager->sessionExists());
}
}
我能够将问题追溯到以下PHPUnit_Util_PHP::runJob()
方法。我正在运行 PHPUnit 3.7.5,正在调用的runJob
方法是:
/**
* Runs a single job (PHP code) using a separate PHP process.
*
* @param string $job
* @param PHPUnit_Framework_TestCase $test
* @param PHPUnit_Framework_TestResult $result
* @return array|null
* @throws PHPUnit_Framework_Exception
*/
public function runJob($job, PHPUnit_Framework_Test $test = NULL, PHPUnit_Framework_TestResult $result = NULL)
{
$process = proc_open(
$this->getPhpBinary(),
array(
0 => array('pipe', 'r'),
1 => array('pipe', 'w'),
2 => array('pipe', 'w')
),
$pipes
);
if (!is_resource($process)) {
throw new PHPUnit_Framework_Exception(
'Unable to create process for process isolation.'
);
}
if ($result !== NULL) {
$result->startTest($test);
}
$this->process($pipes[0], $job);
fclose($pipes[0]);
$stdout = stream_get_contents($pipes[1]);
fclose($pipes[1]);
$stderr = stream_get_contents($pipes[2]);
fclose($pipes[2]);
proc_close($process);
$this->cleanup();
if ($result !== NULL) {
$this->processChildResult($test, $result, $stdout, $stderr);
} else {
return array('stdout' => $stdout, 'stderr' => $stderr);
}
}
该行$stdout = stream_get_contents($pipes[1]);
导致$stdout
等于一长串?
。$this->processChildResult
$stdout
中的值是未序列化的,传递给此函数的无效值会触发警告,从而导致引发异常。我还能够确定$this->getPhpBinary()
的返回值是/usr/bin/php
。
引发异常消息:
PHPUnit_Framework_Exception: ???...???"*???...??
Caused by
ErrorException: unserialize(): Error at offset 0 of 10081 bytes
多亏了 hek2mgl,$job
中的 PHP 代码可以在此要点中查看,其中包含$job var_dump的输出。我创建了一个链接,因为它是相当数量的代码,这个问题已经很长了。
我已经到了这个特定领域的知识的尽头,不确定如何进一步调试这个问题。我不确定为什么@runInSeparateProcess
失败,为什么运行单独进程的$stdout
会导致一长串?标志着。可能导致此问题的原因是什么,我该如何解决它?我在这个模块上处于停滞状态,因为未来的测试将需要在单独的进程中运行,以确保启动和销毁的会话不会影响测试。
- PHP: 5.3.15
- PHPUnit: 3.7.5
- IDE 和命令行测试运行程序中导致错误
好吧,我想通了这里发生了什么,男孩是不是很糟糕。
在测试期间,框架尝试保留全局状态。在 PHPUnit_Framework_TestCase::run()
函数中,当前全局变量通过 PHPUnit_Util_GlobalState::getGlobalsAsString()
转换为 PHP 代码字符串。我目前正在使用自动加载库来处理需要适当的类文件。此类是在全局范围内的变量中声明的,导致将该对象的序列化放入 getGlobalsAsString()
创建的$GLOBALS
数组中。无论出于何种原因,此序列化都包括空字节字符'u0000
。当您尝试反序列化它时,您会收到错误,并最终导致输出也损坏。
我通过在测试引导程序中创建一个函数来解决此问题,该函数在全局范围之外创建自动加载对象。现在,脚本不再尝试反序列化空字节,脚本不会产生任何错误,并且测试用例按预期工作。这是一个非常时髦的问题,不确定是我的代码中的错误,PHPUnit的代码还是serialize
函数的内部错误。
或者,您可以重写测试用例中的 run()
函数,并将测试用例显式设置为不保留全局状态。这可能是一个更干净的解决方案,因为您很容易遇到其他问题,再次需要所有包含的文件,这是默认行为。
class ManagerTest extends PHPUnit_Framework_TestCase {
public function run(PHPUnit_Framework_TestResult $result = null) {
$this->setPreserveGlobalState(false);
parent::run($result);
}
}
必须在调用 PHPUnit_Framework_TestCase::run()
函数之前设置全局状态。这可能是正确可靠地设置全局状态保留的唯一方法。
PHPUnit_Util_PHP::runJob()
$job参数是由 PHPUnit 生成的 php 代码,它以一种可以作为命令行脚本执行的方式包装测试方法的代码(聪明的技巧!隔离测试会将有关测试结果的序列化 php 数据输出回 phpunit。
如果要在顶部添加以下行,则可以调查此代码PHPUnit_Util_PHP::runJob()
file_put_contents('isolated.php', $job);
再次执行失败的测试后,应在当前目录中创建文件isolated.php
。你可以看看代码可以执行它(在当前文件夹中)通过键入
php isolated.php
您是否看到任何看起来凌乱的输出?
此外,您还应该调试方法PHPUnit_Util_PHP::processChildResult()
。该方法负责反序列化和检查测试结果。在那里放置一些回显或var_dumps或使用调试器。查看此方法时,问题很可能是由测试方法的非法输出引起的。
请尝试在php.ini中设置:
detect_unicode = 关闭
为我工作。
测试的代码调用 exit
时会抛出相同的错误是不值得的。