为单元测试交换父类


Swap parent class for unit testing

我有以下情况。假设我有两个类:

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文件中开始。