PHPUnit单元测试对桩件(stub)和仿件对象(Mock)的理解
2018-03-16 11:46
441 查看
来源:http://blog.csdn.net/loophome/article/details/52198716
仿件对象(Mock):将对象替换为能验证预期行为(例如断言某个方法必会被调用)的测试替身的实践方法称为模仿(mocking)。可以用 仿件对象(mock object)“作为观察点来核实被测试系统在测试中的间接输出。通常,仿件对象还需要包括桩件的功能,因为如果测试尚未失败则仿件对象需要向被测系统返回一些值,但是其重点还是在对间接输出的核实上。因此,仿件对象远不止是桩件加断言,它是以一种从根本上完全不同的方式来使用的”(Gerard Meszaros)。
interface IFruit
{
//获取水果的单格
public function getPrice();
//计算购买$number个单位的水果是需要的价格
public function buy($number);
}
Apple类[php] view plain copynamespace Test;
class Apple implements IFruit
{
public function getPrice()
{
return 5.6;
}
public function buy($number)
{
return $number * $this->getPrice();
}
}
Custom类[php] view plain copynamespace Test;
class Custom
{
private $totalMoney;
public function __construct($money)
{
$this->totalMoney = $money;
}
public function buy(IFruit $fruit, $number)
{
$needMoney = $fruit->buy($number);
if ($needMoney > $this->totalMoney) {
return false;
} else {
$this->totalMoney -= $needMoney;
return true;
}
}
}
接下来,我们为Custom类写一个单元测试类,测试购买10个单位的苹果,由于苹果单价为5.6元,即需要56元。但是Custom只有50元,无法购买,因此testBuy方法断言为false。[php] view plain copyuse \Test\Apple;
use \Test\Custom;
class CustomTest extends PHPUnit_Framework_TestCase
{
protected $custom;
public function setUp()
{
$this->custom = new Custom(50);
}
public function testBuy()
{
$result = $this->custom->buy(new Apple(), 10);
$this->assertFalse($result);
}
}
这个测试用例,看起来并没有什么问题,运行起来也是非常正常。但是,可以发现一点就是Custom的测试用例中,把Apple类耦合到一起了。假如Apple获取单价的方法是从某些数据源中获取,那么测试Custom类还需要Apple类的运行环境,增加单元测试的开发难度。
[php] view plain copypublic function testBuy()
{
$appleStub = $this->createMock(Apple::class);
$appleStub->method("buy")
->willReturn(5.6 * 10);
$result = $this->custom->buy($appleStub, 10);
$this->assertFalse($result);
}
$appleStub是一个桩件,模拟了Apple类的buy方法,并且强制指定了返回值56,减少了对Apple类的依赖。
{
$appleMock = $this->getMockBuilder(Apple::class)
->setMethods(['getPrice', 'buy'])
->getMock();
//建立预期情况,buy方法会被调用一次
$appleMock->expects($this->once())
->method('buy');
$this->custom->buy($appleMock, 10);
}
一、桩件(stub)和仿件对象(Mock)概念
桩件(stub):将对象替换为(可选地)返回配置好的返回值的测试替身的实践方法称为上桩(stubbing)。可以用桩件(stub)来“替换掉被测系统所依赖的实际组件,这样测试就有了对被测系统的间接输入的控制点。这使得测试能强制安排被测系统的执行路径,否则被测系统可能无法执行”。仿件对象(Mock):将对象替换为能验证预期行为(例如断言某个方法必会被调用)的测试替身的实践方法称为模仿(mocking)。可以用 仿件对象(mock object)“作为观察点来核实被测试系统在测试中的间接输出。通常,仿件对象还需要包括桩件的功能,因为如果测试尚未失败则仿件对象需要向被测系统返回一些值,但是其重点还是在对间接输出的核实上。因此,仿件对象远不止是桩件加断言,它是以一种从根本上完全不同的方式来使用的”(Gerard Meszaros)。
二、简单的测试例子
我们先建立一个简单的测试例子,以购买水果为例。IFruit接口:[php] view plain copynamespace Test;interface IFruit
{
//获取水果的单格
public function getPrice();
//计算购买$number个单位的水果是需要的价格
public function buy($number);
}
Apple类[php] view plain copynamespace Test;
class Apple implements IFruit
{
public function getPrice()
{
return 5.6;
}
public function buy($number)
{
return $number * $this->getPrice();
}
}
Custom类[php] view plain copynamespace Test;
class Custom
{
private $totalMoney;
public function __construct($money)
{
$this->totalMoney = $money;
}
public function buy(IFruit $fruit, $number)
{
$needMoney = $fruit->buy($number);
if ($needMoney > $this->totalMoney) {
return false;
} else {
$this->totalMoney -= $needMoney;
return true;
}
}
}
接下来,我们为Custom类写一个单元测试类,测试购买10个单位的苹果,由于苹果单价为5.6元,即需要56元。但是Custom只有50元,无法购买,因此testBuy方法断言为false。[php] view plain copyuse \Test\Apple;
use \Test\Custom;
class CustomTest extends PHPUnit_Framework_TestCase
{
protected $custom;
public function setUp()
{
$this->custom = new Custom(50);
}
public function testBuy()
{
$result = $this->custom->buy(new Apple(), 10);
$this->assertFalse($result);
}
}
这个测试用例,看起来并没有什么问题,运行起来也是非常正常。但是,可以发现一点就是Custom的测试用例中,把Apple类耦合到一起了。假如Apple获取单价的方法是从某些数据源中获取,那么测试Custom类还需要Apple类的运行环境,增加单元测试的开发难度。
三、使用桩件(stub)减少依赖
使用桩件后的测试用例:[php] view plain copypublic function testBuy()
{
$appleStub = $this->createMock(Apple::class);
$appleStub->method("buy")
->willReturn(5.6 * 10);
$result = $this->custom->buy($appleStub, 10);
$this->assertFalse($result);
}
$appleStub是一个桩件,模拟了Apple类的buy方法,并且强制指定了返回值56,减少了对Apple类的依赖。
四、仿件对象(Mock)
使用仿件对象,主要的功能是验证预期的行为,作为观察点来核实被测试系统在测试中的间接输出。[php] view plain copypublic function testMockBuy(){
$appleMock = $this->getMockBuilder(Apple::class)
->setMethods(['getPrice', 'buy'])
->getMock();
//建立预期情况,buy方法会被调用一次
$appleMock->expects($this->once())
->method('buy');
$this->custom->buy($appleMock, 10);
}
相关文章推荐
- PHPUnit单元测试对桩件(stub)和仿件对象(Mock)的理解
- 使用Mock对象进行单元测试
- Mock和Stub的初步理解
- 使用MOCK对象进行单元测试
- 使用MOCK对象进行单元测试
- PHPUnit学习03---使用Mock对象解决测试依赖
- 单元测试之Stub和Mock
- [原创]基于mock对象和JUnit框架简化Spring Web组件单元测试
- 使用MOCK对象进行单元测试
- 单元测试之Stub和Mock
- 基于mock对象和JUnit框架简化Spring Web组件单元测试
- 【PHP】PHPUnit单元测试利器:PHP Mock的使用方法
- 基于mock对象和JUnit框架简化Spring Web组件单元测试
- Mockito 单元测试简介以及Stub
- 单元测试(四)单元测试之桩(stub)和模拟(mock)
- 我对Stub和Mock的理解
- 使用MOCK对象进行单元测试
- 基于mock对象和JUnit框架简化Spring Web组件单元测试
- 基于mock对象和JUnit框架简化Spring Web组件单元测试
- 使用 MOCK 对象进行单元测试