W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
將對(duì)象替換為能驗(yàn)證預(yù)期行為(例如斷言某個(gè)方法必會(huì)被調(diào)用)的測(cè)試替身的實(shí)踐方法稱為模仿(mocking)。
可以用 仿件對(duì)象(mock object)“作為觀察點(diǎn)來核實(shí)被測(cè)試系統(tǒng)在測(cè)試中的間接輸出。通常,仿件對(duì)象還需要包括樁件的功能,因?yàn)槿绻麥y(cè)試尚未失敗則仿件對(duì)象需要向被測(cè)系統(tǒng)返回一些值,但是其重點(diǎn)還是在對(duì)間接輸出的核實(shí)上。因此,仿件對(duì)象遠(yuǎn)不止是樁件加斷言,它是以一種從根本上完全不同的方式來使用的”(Gerard Meszaros)。
PHPUnit只會(huì)對(duì)在某個(gè)測(cè)試的作用域內(nèi)生成的仿件對(duì)象進(jìn)行自動(dòng)校驗(yàn)。諸如在數(shù)據(jù)供給器內(nèi)生成或用@depends
標(biāo)注注入測(cè)試的仿件對(duì)象,PHPUnit并不會(huì)自動(dòng)對(duì)其進(jìn)行校驗(yàn)。
這有個(gè)例子:假設(shè)需要測(cè)試的當(dāng)前方法,在例子中是 update()
,確實(shí)在一個(gè)觀察著另外一個(gè)對(duì)象的對(duì)象中上被調(diào)用了。Example?9.10, “被測(cè)系統(tǒng)(SUT)中 Subject 與 Observer 類的代碼”展示了被測(cè)系統(tǒng)(SUT)中 Subject
和 Observer
兩個(gè)類的代碼。
Example?9.10.?被測(cè)系統(tǒng)(SUT)中 Subject 與 Observer 類的代碼
<?php
class Subject
{
protected $observers = array();
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()
{
// 做點(diǎn)什么。
// ...
// 通知觀察者。
$this->notify('something');
}
public function doSomethingBad()
{
foreach ($this->observers as $observer) {
$observer->reportError(42, 'Something bad happened', $this);
}
}
protected function notify($argument)
{
foreach ($this->observers as $observer) {
$observer->update($argument);
}
}
// 其他方法。
}
class Observer
{
public function update($argument)
{
// 做點(diǎn)什么。
}
public function reportError($errorCode, $errorMessage, Subject $subject)
{
// 做點(diǎn)什么。
}
// 其他方法。
}
?>
Example?9.11, “測(cè)試某個(gè)方法會(huì)以特定參數(shù)被調(diào)用一次”展示了如何用仿件對(duì)象來測(cè)試 Subject
和 Observer
對(duì)象之間的互動(dòng)。
首先用 PHPUnit_Framework_TestCase
類提供的 getMock()
方法建立 Observer
的仿件對(duì)象。由于給出了一個(gè)數(shù)組做為 getMock()
方法的第二(可選)參數(shù),Observer
類只有 update()
方法會(huì)被替換為仿實(shí)現(xiàn)。
由于關(guān)注的是檢驗(yàn)?zāi)硞€(gè)方法是否被調(diào)用,以及調(diào)用時(shí)具體所使用的參數(shù),因此引入 expects()
與 with()
方法來指明此交互應(yīng)該是什么樣的。
Example?9.11.?測(cè)試某個(gè)方法會(huì)以特定參數(shù)被調(diào)用一次
<?php
class SubjectTest extends PHPUnit_Framework_TestCase
{
public function testObserversAreUpdated()
{
// 為 Observer 類建立仿件對(duì)象,只模仿 update() 方法。
$observer = $this->getMockBuilder('Observer')
->setMethods(array('update'))
->getMock();
// 建立預(yù)期狀況:update() 方法將會(huì)被調(diào)用一次,
// 并且將以字符串 'something' 為參數(shù)。
$observer->expects($this->once())
->method('update')
->with($this->equalTo('something'));
// 創(chuàng)建 Subject 對(duì)象,并將模仿的 Observer 對(duì)象連接其上。
$subject = new Subject('My subject');
$subject->attach($observer);
// 在 $subject 對(duì)象上調(diào)用 doSomething() 方法,
// 預(yù)期將以字符串 'something' 為參數(shù)調(diào)用
// Observer 仿件對(duì)象的 update() 方法。
$subject->doSomething();
}
}
?>
with()
方法可以攜帶任何數(shù)量的參數(shù),對(duì)應(yīng)于被模仿的方法的參數(shù)數(shù)量??梢詫?duì)方法的參數(shù)指定更加高等的約束而不僅是簡(jiǎn)單的匹配。
Example?9.12.?測(cè)試某個(gè)方法將會(huì)以特定數(shù)量的參數(shù)進(jìn)行調(diào)用,并且對(duì)各個(gè)參數(shù)以多種方式進(jìn)行約束
<?php
class SubjectTest extends PHPUnit_Framework_TestCase
{
public function testErrorReported()
{
// 為 Observer 類建立仿件,對(duì) reportError() 方法進(jìn)行模仿
$observer = $this->getMockBuilder('Observer')
->setMethods(array('reportError'))
->getMock();
$observer->expects($this->once())
->method('reportError')
->with(
$this->greaterThan(0),
$this->stringContains('Something'),
$this->anything()
);
$subject = new Subject('My subject');
$subject->attach($observer);
// doSomethingBad() 方法應(yīng)當(dāng)會(huì)通過(observer的)reportError()方法
//向 observer 報(bào)告錯(cuò)誤。
$subject->doSomethingBad();
}
}
?>
withConsecutive()
方法可以接受任意多個(gè)數(shù)組作為參數(shù),具體數(shù)量取決于欲測(cè)試的調(diào)用。每個(gè)數(shù)組都都是對(duì)被仿方法的相應(yīng)參數(shù)的一組約束,就像 with()
中那樣。
Example?9.13.?測(cè)試某個(gè)方法將會(huì)以特定參數(shù)被調(diào)用二次
<?php
class FooTest extends PHPUnit_Framework_TestCase
{
public function testFunctionCalledTwoTimesWithSpecificArguments()
{
$mock = $this->getMockBuilder('stdClass')
->setMethods(array('set'))
->getMock();
$mock->expects($this->exactly(2))
->method('set')
->withConsecutive(
array($this->equalTo('foo'), $this->greaterThan(0)),
array($this->equalTo('bar'), $this->greaterThan(0))
);
$mock->set('foo', 21);
$mock->set('bar', 48);
}
}
?>
callback()
約束用來進(jìn)行更加復(fù)雜的參數(shù)校驗(yàn)。此約束的唯一參數(shù)是一個(gè) PHP 回調(diào)項(xiàng)(callback)。此 PHP 回調(diào)項(xiàng)接受需要校驗(yàn)的參數(shù)作為其唯一參數(shù),并應(yīng)當(dāng)在參數(shù)通過校驗(yàn)時(shí)返回 TRUE
,否則返回 FALSE
。
Example?9.14.?更加復(fù)雜的參數(shù)校驗(yàn)
<?php
class SubjectTest extends PHPUnit_Framework_TestCase
{
public function testErrorReported()
{
// 為 Observer 類建立仿件,模仿 reportError() 方法
$observer = $this->getMockBuilder('Observer')
->setMethods(array('reportError'))
->getMock();
$observer->expects($this->once())
->method('reportError')
->with($this->greaterThan(0),
$this->stringContains('Something'),
$this->callback(function($subject){
return is_callable(array($subject, 'getName')) &&
$subject->getName() == 'My subject';
}));
$subject = new Subject('My subject');
$subject->attach($observer);
// doSomethingBad() 方法應(yīng)當(dāng)會(huì)通過(observer的)reportError()方法
//向 observer 報(bào)告錯(cuò)誤。
$subject->doSomethingBad();
}
}
?>
Example?9.15.?測(cè)試某個(gè)方法將會(huì)被調(diào)用一次,并且以某個(gè)特定對(duì)象作為參數(shù)。
<?php
class FooTest extends PHPUnit_Framework_TestCase
{
public function testIdenticalObjectPassed()
{
$expectedObject = new stdClass;
$mock = $this->getMockBuilder('stdClass')
->setMethods(array('foo'))
->getMock();
$mock->expects($this->once())
->method('foo')
->with($this->identicalTo($expectedObject));
$mock->foo($expectedObject);
}
}
?>
Example?9.16.?創(chuàng)建仿件對(duì)象時(shí)啟用參數(shù)克隆
<?php
class FooTest extends PHPUnit_Framework_TestCase
{
public function testIdenticalObjectPassed()
{
$cloneArguments = true;
$mock = $this->getMockBuilder('stdClass')
->enableArgumentCloning()
->getMock();
// 現(xiàn)在仿件將對(duì)參數(shù)進(jìn)行克隆,因此 identicalTo 約束將會(huì)失敗。
}
}
?>
Table?A.1, “約束條件”列出了可以應(yīng)用于方法參數(shù)的各種約束,Table?9.1, “匹配器”列出了可以用于指定調(diào)用次數(shù)的各種匹配器。
Table?9.1.?匹配器
匹配器 | 含義 |
---|---|
PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount any() | 返回一個(gè)匹配器,當(dāng)被評(píng)定的方法執(zhí)行0次或更多次(即任意次數(shù))時(shí)匹配成功。 |
PHPUnit_Framework_MockObject_Matcher_InvokedCount never() | 返回一個(gè)匹配器,當(dāng)被評(píng)定的方法從未執(zhí)行時(shí)匹配成功。 |
PHPUnit_Framework_MockObject_Matcher_InvokedAtLeastOnce atLeastOnce()` | 返回一個(gè)匹配器,當(dāng)被評(píng)定的方法執(zhí)行至少一次時(shí)匹配成功。 |
PHPUnit_Framework_MockObject_Matcher_InvokedCount once() | 返回一個(gè)匹配器,當(dāng)被評(píng)定的方法執(zhí)行恰好一次時(shí)匹配成功。 |
PHPUnit_Framework_MockObject_Matcher_InvokedCount exactly(int $count) | 返回一個(gè)匹配器,當(dāng)被評(píng)定的方法執(zhí)行恰好 $count 次時(shí)匹配成功。 |
PHPUnit_Framework_MockObject_Matcher_InvokedAtIndex at(int $index) | 返回一個(gè)匹配器,當(dāng)被評(píng)定的方法是第 $index 個(gè)執(zhí)行的方法時(shí)匹配成功。 |
Note
at()
匹配器的$index
參數(shù)指的是對(duì)給定仿件對(duì)象的所有方法的調(diào)用的索引,從零開始。使用這個(gè)匹配器要謹(jǐn)慎,因?yàn)樗赡軐?dǎo)致測(cè)試由于與具體的實(shí)現(xiàn)細(xì)節(jié)過分緊密綁定而變得脆弱。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: