这是我正在进行单元测试的课程。目前我正在测试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
而不是使用真实的。{/ p>
我没有看到任何方法来注射我的嘲笑UniqueConnection
。我会为UniqueConnection
创建FooClass
构造函数参数,但正如您所看到的,根据doSomething
函数的参数创建一个新参数,并且可能是所有唯一ID被叫的人不知道提前知道。
我能看到的唯一选择是测试FooClass
而不是FooClass
本身的模拟。然后我将getUniqueConnection
函数替换为返回模拟/存根的函数。这对于模拟测试来说似乎很糟糕,但我没有看到任何方法来实现我的目标。 UniqueConnection
是第三方供应商库,无法修改。
答案 0 :(得分:4)
您可以创建一个UniqueConnectionFactory
,并将其实例传递给FooClass。那你有
private function getUniqueConnection( $id ) {
return $this->uniqueConnectionFactory->create( $id );
}
一般来说,这是使用工厂的好处之一 - 您将new
运算符保留在类之外,这样您就可以更轻松地更改正在创建的对象。
答案 1 :(得分:2)
在您可以花时间重构代码以使用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();
}
答案 2 :(得分:2)
就像Rambo Coder说的那样,这是你班上做得太多的问题。我不会想要创建一个工厂,特别是如果你只创建一个一个特定类的实例。最简单的解决方案是颠倒创建UniqueConnection的责任:
<?php
class FooClass {
public function doSomething( UniqueConnection $connection ) {
$connection->doSomethingDestructive( );
}
}
在测试时传递模拟,在实际代码中传递new UniqueConnection( $user->id )
..
答案 3 :(得分:0)
在某些情况下,以支持不同执行模式的方式创建类非常重要。其中一个案例就是你要求的。
创建类以支持各种模式。例如
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
}
答案 4 :(得分:0)
在这种情况下,您想要的不是模拟对象,而是测试子类。将$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 );
}
}