Phpunit中的Mocking
在介绍Mocking前,先来看下为什么要使用Mocking。举一个数据库查询的例子,比如在某个应用中,如果要测试一个数据库的应用,但假如这个数据库的测试要耗费很多资源以及编写很复杂的单元测试的代码的话,可以尝试使用Mocking技术。举例说明如下:
class Database
{
public function reallyLongTime()
{
$results = array(
array(1,'test','foo value')
);
sleep(100);
return $results;
}
}
?>
在上面这个例子中,我们模拟了一个数据库的操作,认为它需要运行很长时间。接下来我们编写其单元测试代码如下:
require_once '/path/to/Database.php';
class DatabaseTest extends PHPUnit_Framework_TestCase
{
private $db = null;
public function setUp()
{
$this->db = new Database();
}
public function tearDown()
{
unset($this->db);
}
/**
* Test that the "really long query" always returns values
*/
public function testReallyLongReturn()
{
$mock = $this->getMock('Database');
$result = array(
array(1,'foo','bar test')
);
$mock->expects($this->any())
->method('reallyLongTime')
->will($this->returnValue($result));
$return = $mock->reallyLongTime();
$this->assertGreaterThan(0,count($return));
}
}
?>
注意看这段代码中有趣的地方,这里,使用了phpunit中的getMock对象方法,这里实际上是模拟生成一个Database类的“伪实例”了,这里生成了$mock这个mock对象实例,以方便接着的单元测试中用到。接下来的这三行代码:
->method('reallyLongTime')
->will($this->returnValue($result));
它们的含义为:无论方法reallyLongtime执行了多长时间,始终最后会直接返回$result这个数组的结果。这样,你就可以通过mocking技术很轻易地去实现在单元测试中,绕过某些复杂的逻辑部分,而节省大量的宝贵时间提高测试效率。
下面的这个例子,讲解的是Mocking技术中的更高级用法Mockbuilder。依然以上面的例子说明:
public function testReallyLongRunBuilder()
{
$stub = $this->getMockBuilder('Database')
->setMethods(array(
'reallyLongTime'
))
->disableAutoload()
->disableOriginalConstructor()
->getMock();
$result = array(array(1,'foo','bar test'));
$stub->expects($this->any())
->method('reallyLongTime')
->will($this->returnValue($result));
$this->assertGreaterThan(0,count($return));
}
?>
通过使用Mockbuilder,我们可以不用通过构造函数的方法去初始化一个mock对象。这段代码跟上一段代码的功能其实是一样的,只不过留意一下新的两个方法: disableAutoload和disableOriginalConstructor,其功能分别是禁止使用php的内置的autoload初始构造方法和禁止调用该类原有的构造函数。最后再看一个例子:
/**
* Testing enforcing the type to "array" like the "enforceTypes"
* method does via type hinting
*/
public function ttestReallyLongRunBuilderConstraint()
{
$stub = $this->getMock('Database',array('reallyLongTime'));
$stub->expects($this->any())
->method('reallyLongTime')
->with($this->isType('array'));
$arr = array('test');
$this->assertTrue($stub-> reallyLongTime ($arr));
}
?>
在这里,我们使用了with方法,其中这个方法中指定了要传入的参数类型为array数组类型,最后这个断言是通过了,因为返回的的确是数组类型。
更多的关于phpunit中mock的用法,请参考phpunit手册中第11章的论述。