这是我正在单元测试的类。 目前我正在测试doSomething
功能:
class FooClass {
public function doSomething( $user ) {
$conn = $this->getUniqueConnection( $user->id );
$conn->doSomethingDestructive();
}
private function getUniqueConnection( $id ) {
return new UniqueConnection( $id );
}
}
如您所见,doSomething
函数根据它收到的参数的属性获取 UniqueConnection
的新实例(我在这里不测试的类)。 问题是UniqueConnection:: doSomethingDestructive
方法是我在测试期间无法调用的,因为它......破坏性。 所以我想存根/模拟UniqueConnection
而不是使用真实的。
看不出有什么方法可以注入我被嘲笑的UniqueConnection
. 我会将UniqueConnection
作为FooClass
的构造函数参数,但是,如您所见,根据doSomething
函数的参数创建了一个新参数,并且可能调用它的所有唯一id都不知道提前。
我能看到的唯一选择是测试FooClass
的模拟,而不是FooClass
本身。 然后我会用返回模拟/存根的函数替换 getUniqueConnection
函数。 测试模拟似乎很糟糕,但我看不到任何方法可以实现我所追求的目标。 UniqueConnection
是第三方供应商库,无法修改。
你可以做一个UniqueConnectionFactory
,并将它的实例传递给FooClass。然后你有
private function getUniqueConnection( $id ) {
return $this->uniqueConnectionFactory->create( $id );
}
通常,这是使用工厂的好处之一 - 您将new
运算符排除在类之外,这使您可以更轻松地更改正在创建的对象。
就像 Rambo Coder 说的,这是在课堂上做得太多的问题。我不会想要创建一个工厂,特别是如果你只创建一个特定类的实例。最简单的解决方案是反转创建 UniqueConnection 的责任:
<?php
class FooClass {
public function doSomething( UniqueConnection $connection ) {
$connection->doSomethingDestructive( );
}
}
测试时通过模拟,在真实代码中传递new UniqueConnection( $user->id )
。
在您可以花时间重构代码以按照 rambo 编码器的建议使用工厂之前,您可以使用部分模拟来返回非破坏性的唯一连接。当你发现自己处于这个位置时,这通常意味着被测班级有不止一项责任。
function testSomething() {
$mockConn = $this->getMock('UniqueConnection');
$mockConn->expects($this->once())
->method('doSomethingDestructive')
->will(...);
$mockFoo = $this->getMock('FooClass', array('getUniqueConnection'));
$mockFoo->expects($this->once())
->method('getUniqueConnection')
->will($this->returnValue($mockConn));
$mockFoo->doSomething();
}
以支持不同执行模式的方式创建类非常重要。其中一种情况就是您所要求的。
创建类以支持各种模式。例如
Class Connection {
private $mode;
public function setMode($mode) {
$this -> $mode = $mode;
}
}
现在,您的doSomethingDestructive
可以根据执行模式进行操作。
public function doSomethingDestructive() {
if($this -> mode === "test") { //if we are in a test mode scenario
//Log something
// Or just do some logging and give a message
} else {
// do what it was suppose to do
}
}
下次,当你测试这个类时,你不必担心破坏性函数会意外地破坏一些东西。
public function doSomething( $user ) {
$conn = $this->getUniqueConnection( $user->id );
$conn -> setMode("test"); //Now we are safe
$conn->doSomethingDestructive(); //But the Testing is still being Ran
}
在这种情况下,你想要的不是一个模拟对象,而是一个测试子类。将$conn->doSomethingDestructive();
分解为一个方法,然后将子类FooClass
为TestFooClass
,并覆盖子类中的新方法。然后,您可以使用子类进行测试,而不会获得不需要的破坏性行为。
例如:
class FooClass {
public function doSomething( $user ) {
$conn = $this->getUniqueConnection( $user->id );
$this->connDoSomethingDestructive($conn);
}
protected function connDoSomethingDestructive($conn) {
$conn->doSomethingDestructive();
}
private function getUniqueConnection( $id ) {
return new UniqueConnection( $id );
}
}
class TestFooClass extends FooClass {
protected function connDoSomethingDestructive() {
}
private function getUniqueConnection( $id ) {
return new MockUniqueConnection( $id );
}
}