我有以下情况。假设我有两个类:
class Session {
public function start() {
return session_start();
}
// methods for all the other session functions of PHP
}
和继承Session
class TrustedSession extends Session {
public function start() {
if(parent::start() === false)
return false;
$requestRemoteAddress = isset($_SERVER['REMOTE_ADDR'])?$_SERVER['REMOTE_ADDR']:null;
$requestUserAgent = isset($_SERVER['HTTP_USER_AGENT'])?$_SERVER['HTTP_USER_AGENT']:null;
$trustedRemoteAddress = $this->get('TRUSTED_REMOTE_ADDR');
$prevUserAgent = $this->get('PREV_USERAGENT');
if($trustedRemoteAddress !== $requestRemoteAddress || $prevUserAgent !== $requestUserAgent)
$this->regenerateID(true);
return true;
}
}
所以不,我想测试我的TrustedSession
类。但是我必须使用Session
的模拟,因为根据这个问题,我不能使用PHP的真正会话函数,因为它们会导致PHPUnit的Output already started
错误。
现在的问题是:如何模拟Session
类。我的意思是我可以创建一个SessionInterface
,它定义了我必须为Session
和它的模型实现的方法。但是我怎么能用模型扩展我的TrustedSession
类呢?
我的想法是在运行时交换父类,但我认为这是不可能的(至少在PHP中)。这是一个设计问题,我正在考虑我上面描述的界面,但这并不能帮助我摆脱这种情况。有没有一个"干净"又好的解决方案?
定义一个接口并使Session成为TrustedSession的依赖项:
interface ISession
{
public function start();
}
class Session implements ISession
{
public function start()
{
return session_start();
}
}
class TrustedSession implements ISession
{
private $session;
public function __construct(Session $session)
{
$this->session = $session;
}
public function start()
{
if (!$this->session->start()) {
return false;
}
$requestRemoteAddress = isset($_SERVER['REMOTE_ADDR'])?$_SERVER['REMOTE_ADDR']:null;
....
return true;
}
}
这样你就可以在测试TrustedSession
我自己回答这个问题,因为当使用PHPUnit与PHP函数(如session_start()
)相结合进行测试时,我找到了一种方法来解决Output already started
的这个问题。
它真的很简单,但也很脏,而且肯定不干净。我所做的是使用phpunit.xml
s选项来执行一个引导文件。
<phpunit bootstrap="bootstrap.php" colors="true">
<!-- testsuites here -->
</phpunit>
在bootstrap.php
文件中,我放入这些代码行:
ob_start();
// Composer autoload file
require '../vendor/autoload.php';
现在PHPUnit发送给客户端的所有输出都被缓存,直到脚本结束。这适用于3.7.*
版本的PHPUnit。没有测试版本4.x
,因为我使用PHPStorm进行开发。
这也只适用于标准单元测试,它与代码覆盖失败,因为在代码覆盖模块的某个地方有一个强制"清理并发送到客户端"的输出缓冲,我在我的bootstrap.php
文件中开始。