php單元測(cè)試進(jìn)階(10)- 核心技術(shù) - 樁件(stub) - 調(diào)用方法注入樁件
本系列文章主要代碼與文字來(lái)源于《單元測(cè)試的藝術(shù)》,原作者:Roy Osherove。譯者:金迎梨撞。
本系列文章根據(jù)php的語(yǔ)法與使用習(xí)慣做了改編贰剥。所有代碼在本機(jī)測(cè)試通過(guò)弹沽。如轉(zhuǎn)載請(qǐng)注明出處畴嘶。
使用本方法的步驟如下。
在被測(cè)試類中:
- 添加一個(gè)返回真實(shí)實(shí)例的方法
- 正常的在代碼中使用該方法
在測(cè)試代碼中:
- 創(chuàng)建一個(gè)新類繼承被測(cè)類
- 創(chuàng)建一個(gè)你要替換的接口的那個(gè)類型的公共字段
- 重寫(xiě)被測(cè)類的返回真實(shí)實(shí)例的方法
- 返回上面的公共字段
在測(cè)試類中:
- 創(chuàng)建一個(gè)樁件凝垛,此樁件實(shí)現(xiàn)了要替換的接口
- 創(chuàng)建新派生類而非被測(cè)類的實(shí)例
- 配置這個(gè)新的實(shí)例懊悯,注入樁件
與前文比較,上文介紹了工廠類注入樁件梦皮,源代碼4個(gè)文件炭分,測(cè)試代碼2個(gè)文件。
本文介紹調(diào)用方法注入樁件剑肯,源代碼3個(gè)文件捧毛,測(cè)試代碼3個(gè)文件。
一個(gè)接口和兩個(gè)實(shí)現(xiàn)不變让网,被測(cè)類改變呀忧,測(cè)試類改變,新加一個(gè)測(cè)試輔助類即派生類溃睹。
本方法的要點(diǎn):其實(shí)測(cè)試的是子類而账,而不是原本的被測(cè)類。
這種方法比前面幾個(gè)好因篇,因?yàn)樗鼓銦o(wú)需進(jìn)入更深層次(改變調(diào)用棧深處的依賴)即可直接替換依賴項(xiàng)泞辐,實(shí)現(xiàn)起來(lái)干凈快速。
這種方法的缺點(diǎn):非常適合用來(lái)模擬提供給被測(cè)代碼的輸入惜犀,但是如果用來(lái)驗(yàn)證從被測(cè)代碼到依賴項(xiàng)的調(diào)用卻十分不便铛碑。
例如狠裹,如果測(cè)試代碼調(diào)用一個(gè)web服務(wù)虽界,得到一個(gè)返回值,你想要模擬自己的返回值涛菠,使用這個(gè)方法很好莉御。但是如果你想測(cè)試代碼對(duì)web服務(wù)的調(diào)用是否正確撇吞,事情很快就變?cè)懔恕_@需要大量的手工編碼礁叔,而mock框架或類庫(kù)更適合做這樣的工作牍颈。總之琅关,本方法適合模擬返回值或者整個(gè)返回接口煮岁,但不適合檢查對(duì)象之間的交互。
源代碼
(1)t2\application\index\controller下根據(jù)測(cè)試需要(實(shí)際是解耦涣易,讓程序更加結(jié)構(gòu)清晰)提取的接口
IExtensionManager.php(未改動(dòng))
<?php
namespace app\index\controller;
/**
* 文件名是否有效接口
* 源代碼中的文件管理器類會(huì)實(shí)現(xiàn)画机,一個(gè)樁件也會(huì)實(shí)現(xiàn)
* 接口的存在,讓所有代碼的含義更加清晰新症,穩(wěn)定步氏。
*/
interface IExtensionManager
{
/**
* 判斷文件名是否有效
* @param string $filename
* @return boolean
*/
public function isValid($filename);
}
(2)t2\application\index\controller下文件管理器類,實(shí)現(xiàn)了上面的接口徒爹,但是實(shí)際被排除在單元測(cè)試之外荚醒,不測(cè)它。應(yīng)該使用集成測(cè)試來(lái)測(cè)試此類隆嗅。
FileExtensionManager.php(未改動(dòng))
<?php
namespace app\index\controller;
/**
* 文件管理器類
*
*/
class FileExtensionManager implements IExtensionManager
{
/**
* 根據(jù)某個(gè)配置文件的內(nèi)容判斷文件名是否有效
* @param string $filename
*/
public function isValid($filename)
{
// 會(huì)使用file_get_contents函數(shù)讀取某個(gè)文件的內(nèi)容
// 這里為了簡(jiǎn)略不寫(xiě)界阁,因?yàn)椴皇侵攸c(diǎn)。
return true;
}
}
(3)t2\application\index\controller下被測(cè)類胖喳,日志分析器铺董。使用了調(diào)用方法注入的方式來(lái)寫(xiě)代碼,便于派生類覆蓋禀晓,然后測(cè)試
LogAnalyzer.php
<?php
namespace app\index\controller;
/**
* 日志分析器類精续,也是被測(cè)類
*
* 注意,這是用調(diào)用方法注入樁件的例子粹懒。
*/
class LogAnalyzer
{
/**
* 判斷文件名是否有效重付,調(diào)用另一個(gè)類來(lái)實(shí)現(xiàn)
* @param string $filename
*/
public function isValidLogFileName($filename)
{
return $this->getManager()->isValid($filename);
}
protected function getManager()
{
return new FileExtensionManager();
}
}
測(cè)試代碼
(4)t2\tests\index\controller\下,樁件類凫乖,用于替換文件管理器确垫,便于測(cè)試
FakeExtensionManager.php(未改動(dòng))
<?php
namespace tests\index\controller;
/**
* 一個(gè)樁件類,用于測(cè)試日志分析器,因?yàn)槿罩痉治鰰?huì)讀取文件剑鞍,妨礙單元測(cè)試凡蚜。
*/
class FakeExtensionManager implements \app\index\controller\IExtensionManager
{
public $willBeValid = false;
/**
* 根據(jù)某個(gè)配置文件的內(nèi)容判斷文件名是否有效
* @param string $filename
*/
public function isValid($filename)
{
return $this->willBeValid;
}
}
(5)t2\tests\index\controller\下,被測(cè)試類的子類披泪,用于覆蓋產(chǎn)生樁件的方法,便于測(cè)試搬瑰。因?yàn)檫@個(gè)子類測(cè)試專用款票,所以當(dāng)然放在測(cè)試文件夾下控硼。
LogAnalyzerExtend.php
<?php
namespace tests\index\controller;
use app\index\controller\IExtensionManager;
/**
* 測(cè)試輔助類,是源代碼被測(cè)類的子類艾少。用于覆蓋原被測(cè)類的方法卡乾,便于測(cè)試。
* 請(qǐng)注意本類寫(xiě)法缚够,為了代碼更加通用幔妨,使用了構(gòu)造方法注入樁件,而不是寫(xiě)死谍椅。
* 其實(shí)也可以寫(xiě)死陶冷,代碼更少,只是此類就不能復(fù)用了毯辅。
*
* 但是又可以看到埂伦,萬(wàn)一原代碼有構(gòu)造方法呢,沖突咋辦思恐?其實(shí)沒(méi)關(guān)系沾谜。
* 因?yàn)槲覀兛梢詾榱藴y(cè)試源代碼的一個(gè)類的多個(gè)方法,寫(xiě)不同的測(cè)試輔助類來(lái)對(duì)應(yīng)胀莹。
*/
class LogAnalyzerExtend extends \app\index\controller\LogAnalyzer
{
/**
* @var \app\index\controller\IExtensionManager
*/
private $manager;
/**
* 用構(gòu)造方法注入
* @param \app\index\controller\IExtensionManager $mgr
*/
public function __construct($mgr)
{
$this->manager = $mgr;
}
/**
* 覆蓋原方法基跑,便于測(cè)試
*/
protected function getManager()
{
return $this->manager;
}
}
(6)t2\tests\index\controller\下,最后是測(cè)試類描焰,但不是測(cè)試被測(cè)試類媳否,而是測(cè)試被測(cè)試類的子類。
LogAnalyzerTest.php
<?php
namespace tests\index\controller;
/**
* 測(cè)試用的類
*/
class LogAnalyzerTest extends \think\testing\TestCase
{
/**
* @test
* 使用靜態(tài)工廠注入樁件的方法 進(jìn)行測(cè)試
* 注意荆秦,盡量使得測(cè)試的方法名稱有意義篱竭,這非常重要,便于維護(hù)測(cè)試代碼步绸。有規(guī)律
*/
public function isValidFileName_NameSupportedExtension_ReturnTrue()
{
//準(zhǔn)備好一個(gè)返回true的樁件掺逼。
$myFakeManager = new FakeExtensionManager();
$myFakeManager->willBeValid = true;
//開(kāi)始創(chuàng)建被測(cè)類的子類的對(duì)象,注入樁件瓤介,準(zhǔn)備測(cè)試
$analyzer = new LogAnalyzerExtend($myFakeManager); //創(chuàng)建同時(shí)注入
$result = $analyzer->isValidLogFileName("short.ext");
$this->assertTrue($result);
}
}
cmd下測(cè)試通過(guò)吕喘。
上一篇:php單元測(cè)試進(jìn)階(9)- 核心技術(shù) - 樁件(stub) - 工廠類注入樁件
下一篇:php單元測(cè)試進(jìn)階(11)- 核心技術(shù) - 樁件(stub) - 不使用樁件