9 测试替身
PHPUnit 提供的
createMock($type)
和getMockBuilder($type)
方法可以在测试中用来自动生成对象,此对象可以充当任意指定原版类型(接口或类名)的测试替身。在任何预期或要求使用原版类的实例对象的上下文中都可以使用这个测试替身对象来代替。
createMock($type)
方法直接返回指定类型(接口或类)的测试替身对象实例。此测试替身的创建使用了最佳实践的默认值(不执行原始类的__construct()
和__clone()
方法,且不对传递给测试替身的方法的参数进行克隆)。如果这些默认值非你所需,可以用getMockBuilder($type)
方法并使用流畅式接口来定制测试替身的生成过程。在默认情况下,原版类的所有方法都会被替换为只会返回
null
的伪实现(其中不会调用原版方法)。使用诸如will($this->returnValue())
之类的方法可以对这些伪实现在被调用时应当返回什么值做出配置。
简单来说就是一些测试类需要调用其他的类作为参数,但是我们又不想创建那些类,所以PHPUnit提供了一种模仿机制。
桩件:Stubs
将对象替换成配置好对应返回值的替身的方法成为上桩(stubbing)。
设置返回值
可以返回的数据种类有:
- 固定值
- 传入的参数
- 返回桩件类自身
- 根据传入的参数,返回不同的值
- 设置回调函数,计算返回值
- 设置同一方法的多次调用时,每次的返回值
- 设置抛出指定异常
下面是demo
require_once __DIR__."/../../vendor/autoload.php";
use PHPUnit\Framework\TestCase;
class SomeClass
{
public function doSomething()
{
// 随便做点什么。
return "data";
}
public function callUndefinedFunction($param){
return "你不可以调用在原始类中未定义的方法";
}
public function classTest(){
return "self";
}
public function returnValueByParam(){
}
public function returnDataByCallBack(){
}
public function returnDataByList(){
}
public function throwExceptions(){
}
}
class Mock extends TestCase{
public function testExample(){
$stub = $this->createMock(SomeClass::class);
// 返回固定值
$stub->method('doSomething')
->willReturn('foo');
// 返回参数
$stub->method("callUndefinedFunction")
->will($this->returnArgument(0));
// 返回该桩类自身
$stub->method("classTest")
->will($this->returnSelf());
// 根据不同参数,返回不同的值
$map=array(
// 最后一个值永远是返回的结果,而前面的就是传递的参数
array("param1","param2","param3",'result1'),
array('param2','param3','result2'),
array("result3")
);
$stub->method("returnValueByParam")
->will($this->returnValueMap($map));
// 下面调用时传入多少个参数,都会自动传入这里。
// 但是,下面调用的参数只能比这里多,不能少,否则会报错误
$callBackFunction=function ($param1,$param2,$param3){
print_r(array(
$param1,
$param2,
$param3
));
};
$stub->method("returnDataByCallBack")
->will($this->returnCallback($callBackFunction));
// 设置每一次调用的返回值
$stub->method("returnDataByList")
->will($this->onConsecutiveCalls(1,2,3,4,5,6));// 注意,这里传入的不是数组!
// 设置代码抛出异常
$stub->method("throwExceptions")
->will($this->throwException(new \Exception("没事情,就是抛异常玩玩")));
$this->assertEquals('foo', $stub->doSomething());
$this->assertEquals("hello",$stub->callUndefinedFunction("hello"));
$this->assertSame($stub,$stub->classTest());
$this->assertEquals("result1",$stub->returnValueByParam("param1","param2","param3"));
$this->assertEquals("result2",$stub->returnValueByParam('param2','param3'));
$this->assertEquals("result3",$stub->returnValueByParam());
// 这里调用上面设置的方法,其中的参数会自动传入回调函数中
// $stub->returnDataByCallBack(1,2,3,4);
$this->assertEquals(1,$stub->returnDataByList());
$this->assertEquals(2,$stub->returnDataByList());
try{
$stub->throwExceptions();
}catch (Exception $e){
print "\r\n".$e->getMessage()."\r\n";
}
}
}
注意,不能使用$stub->method()
设置对应类中不存在的方法,否则会报错。
创建桩件时进行限制
public function testStubSetting(){
$stub=$this->getMockBuilder(SomeClassSecond::class)
->disableOriginalClone()// 开启设置之后,原类的 __clone 函数就不会运行了
->disableOriginalConstructor()// 开启这个之后,就不会运行原类的 __construct 方法了
->disableArgumentCloning()// 开启这个时候就不会在创建桩件的时候,复制原类的属性
->disallowMockingUnknownTypes()// 不允许定义不存在的属性
->getMock();
$stub2= clone $stub;// 即使上面限制了 disableOriginalClone,但是那只是限制不能运行原类的 __clone,clone函数还是生效的,所以下面的断言是正确的
$this->assertEquals($stub2,$stub,"两个类不一致");
}
仿件对象:Mock Object
仿件对象有桩件的功能,但是比桩件更加贴合原来的类。举个例子就是桩件只能调用设置好的方法,不能调用原类的一些方法,但是仿件对象可以。
创建仿件对象
<?php
require_once __DIR__."/../../vendor/autoload.php";
use PHPUnit\Framework\TestCase;
class Subject
{
protected $observers = [];
protected $name;
public function __construct($name)
{
$this->name = $name;
}
public function getName()
{
return $this->name;
}
public function attach(Observer $observer)
{
$this->observers[] = $observer;
}
public function doSomething()
{
// 做点什么
// ...
// 通知观察者发生了些什么
$this->notify('something');
}
public function doSomethingBad</