我正在构建会话管理类和相关的单元测试。为了将类与$ _SESSION的全局状态分开,我使用一个非常简单的类来管理类和会话数据之间的绑定。
Binding类的完整源代码如下:
class Binding implements iface\Binding
{
public function &getNamespace ($namespace)
{
return $_SESSION [$namespace];
}
}
在消费Session类中,我有以下内容:
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中轻松替换mocks。
我在编写测试时意识到我可以使用模拟绑定类做更多事情而不仅仅是从类中分离会话,我还可以使用它作为观察类的非公共属性而不必实际的方法暴露任何内部状态,从而使类脆弱。由于类操作引用数组而不是直接在数组上操作,因此我可以观察被引用的数组。
我希望用PHPUnit的模拟API做到这一点,但我无法弄清楚如何做到这一点。
我知道我可以创建一个返回如下数组的模拟器:
$mock = $this -> getMock ('iface\Binding');
$mock -> expects ($this -> any ())
-> method ('getNamespace')
-> will ($this -> returnValue (array ()));
虽然观察状态变化没有用,因为它每次都返回一个不同的数组。我需要的是一个mock,每次都返回对同一个数组的引用。
最后,我编写了一个类来取代真正的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中实现相同的功能之外,为单元测试添加额外的类似乎是错误的方法。
答案 0 :(得分:0)
我认为你可以返回一个ArrayObject
,它将内部数组暴露给你的测试,但在这种情况下我宁愿避免将数组从Binding
暴露出来。相反,Binding
接口应提供获取,设置和清除值以及其他更高级别操作的方法。然后你可以传入一个模拟对象,它需要那些调用而不是一个返回原始数组的调用。