2

我正在构建一个会话管理类和相关的单元测试。为了将类与 $_SESSION 的全局状态分开,我使用了一个非常简单的类来管理类和会话数据之间的绑定。

Binding类的整个源码如下:

class Binding implements iface\Binding
{
    public function &getNamespace ($namespace)
    {
        return $_SESSION [$namespace];
    }
}

在消费会话类中,我有以下内容:

protected function initStorage ()
{
    // Check that storage hasn't already been bound to the session
    if ($this -> storage === NULL)
    {
        // Attempt to start the session if it hasn't already been started
        if (($this -> sessionId () !== '')
        || ((!$this -> headersSent ())
        && ($this -> startSession ())))
        {
            // Bind the storage to the session
            $this -> storage    =& $this -> binding -> getNamespace ($this -> namespace);
            // Make sure the session is in a usable state
            if (!$this -> hasData ())
            {
                $this -> reset ();
            }
        }
        else
        {
            // We couldn't start the session
            throw new \RuntimeException (__METHOD__ . ': Unable to initiate session storage at this time');
        }
    }

    return $this;
}

sessionId、headersSent 和 startSession 是用作“测试接缝”的简单单行函数,我可以很容易地用 PHPUnit 中的模拟替换它们。

我在编写测试时意识到,我可以使用模拟绑定类做更多的事情,而不仅仅是将会话从类中分离出来,我还可以将其用作观察类的非公共属性的一种方式,而无需实际暴露任何内部状态,从而使班级变得脆弱。由于该类对数组的引用而不是直接对数组进行操作,因此我可以观察到被引用的数组。

我希望用 PHPUnit 的模拟 API 来做到这一点,但我不知道该怎么做。

我知道我可以创建一个返回这样的数组的模拟:

$mock = $this -> getMock ('iface\Binding');

$mock -> expects ($this -> any ())
      -> method ('getNamespace')
      -> will ($this -> returnValue (array ()));

但是,它对于观察状态变化没有用,因为它每次都返回一个不同的数组。我需要的是一个模拟,它每次都返回对同一个数组的引用。

最后我写了一个类来代替真正的 Binding 类并使用它:

class BindingMock implements iface\Binding
{
    protected $storage = array ();

    public function &getNamespace ($namespace)
    {
        return $this -> storage [$namespace];
    }
}

使用此类可以让我在调用 Session API 之前和之后检查 $storage 的内容,因为我可以查看存储数组中的内容,而无需在 Session 类中公开非公共状态。这是一个使用该技术的示例测试:

public function testCreateItem ()
{
    $storage    =& $this -> binding -> getNamespace ('unittest');
    $this -> assertEmpty ($storage);
    $this -> object ->  createItem ('This is a test', 'test');
    $this -> assertNotEmpty ($storage);
    $this -> assertEquals ('This is a test', $storage ['test']);
}

我宁愿能够使用 PHPUnit 生成替代类,但是,只为单元测试使用附加类似乎是错误的方法,除非在 PHPUnit 中无法实现相同的目标。

4

1 回答 1

0

我认为您可以返回一个ArrayObject将内部数组公开给您的测试的,但在这种情况下,我更愿意避免将数组暴露给测试Binding。相反,Binding接口应该提供方法来获取、设置和清除值以及其他更高级别的操作。然后,您可以传入一个期望这些调用的模拟对象,而不是返回原始数组的模拟对象。

于 2012-08-10T04:29:36.370 回答