王莉軍
(渤海大學(xué) 大學(xué)計(jì)算機(jī)教研部,遼寧 錦州 121013)
一個(gè)函數(shù)、一個(gè)類編寫完成,到底能不能正確工作?怎么測(cè)試它?PHP單元測(cè)試是個(gè)好辦法,它提供了自動(dòng)化測(cè)試的方法,使敏捷開發(fā)的自動(dòng)化測(cè)試成為可能。
1)代碼具備基本可測(cè)試性。及要求被測(cè)試函數(shù)具備輸入輸出。(本測(cè)試方案未考慮無輸入輸出函數(shù)的測(cè)試)
2)被測(cè)函數(shù)盡可能分情況說明輸入輸出。及期望輸入及輸出和非期望輸入對(duì)應(yīng)輸出。
3)被測(cè)還是應(yīng)該有基本的函數(shù)說明,表明函數(shù)的功能[1]。
1)對(duì)于某個(gè)系統(tǒng),不同層的代碼放置于不同文件夾下。以talk為例,其有dataaccess層和logic層,那么其dataaccess層代碼放置于文件夾dataaccess之下。而單元測(cè)試文件的布局則和系統(tǒng)代碼布局一一對(duì)應(yīng)。對(duì)于某個(gè)文件a.php,其對(duì)應(yīng)的測(cè)試文件命名則為aTest.php。而對(duì)于a.php中某個(gè)函數(shù)method來說,其對(duì)應(yīng)的測(cè)試函數(shù)命名應(yīng)該為testMethod[2]。
2)每個(gè)測(cè)試函數(shù)應(yīng)該包括一定的注釋。不依賴于dataprovider的情況。
/**
*@author****
*@note****
*@expect input**
*@expect output**
*@unexpect input**
*@unexpect output**
*/
依賴于dataprovider的情況:
/**
*@author**** /**
*@note**** *@expect 1,2,3
*@dataprovider** *@unexpect 4,5,6
*/ */
1)在測(cè)試根目錄下應(yīng)該包含有各文件夾下文件測(cè)試覆蓋率統(tǒng)計(jì)文件夾。
2)單元測(cè)試代碼應(yīng)該避免過多的依賴關(guān)系。盡量減少對(duì)外部環(huán)境依賴,減少對(duì)外部代碼具體實(shí)現(xiàn)依賴,減少對(duì)測(cè)試內(nèi)部函數(shù)之間的依賴[3]。
場(chǎng)景一:一般簡(jiǎn)單情況的函數(shù)測(cè)試
1)被測(cè)試class如下:
Class MyMathClass
{
/*
**add two given values,and return the sun
*/
Public function add($a,$b)
{
Return$a+$b;
}
}
?>
2)測(cè)試 class如下:
Require_once ‘PHPUnit/Framework.php’;
Require_once ‘MyMathClass.php’;
/**
*Test class for MyMathClass.
*Generated by PHPUnit on 2011-03-31 at 13:11:05
*/
Class MyMathClassTest extends PHPUnit_Framework_Testcase
{
/**
*@var MyMathClass
*@access protected
*/
Protected$object;
/**
* Setsup the fixture,forexample,opens a network connection.
*This method is called before a test is executed.
*
*@access protected
*/
Protected function setup()
{
$this->object–new MyMathClass;
}
/**
* Tears down the fixture,for example,closes a network connection.
*this method is called after a test is executed.
*
*@access protected
*/
Protected function tearDown()
{ }
/**
*@todo ImpLement testAdd().
*/
Public function testAdd(){
//Remove the following lines when you implement this test.
$this->assertEquales(3,$this->object->add(1,2));
}
}
?>
簡(jiǎn)單單元測(cè)試class里僅僅包含一個(gè)被測(cè)試的method的,而在生成的測(cè)試class里邊包含了除對(duì)應(yīng)add函數(shù)的測(cè)試函數(shù)testAdd以外,還包含setUp和tearDown函數(shù)。其中setUp是在每個(gè)測(cè)試函數(shù)執(zhí)行之前都會(huì)自動(dòng)執(zhí)行一遍,用來自動(dòng)建立每個(gè)method的獨(dú)立測(cè)試上下文環(huán)境,通用tearDown在每個(gè)測(cè)試函數(shù)執(zhí)行之后執(zhí)行一遍,用來清除此method執(zhí)行之中設(shè)定的上下文[4]。而testAdd則用來對(duì)add函數(shù)進(jìn)行測(cè)試。testAdd函數(shù)中只包含一條語句,這條語句即假定通過調(diào)用add函數(shù)執(zhí)行1加2,我們期望其返回的結(jié)果與3相等。如果相等,執(zhí)行結(jié)果則通過,如果不相等則測(cè)試失敗,說明代碼并沒有完成我們想要的功能,如圖1所示。
圖1 測(cè)試結(jié)果Fig.1 Test execution results
場(chǎng)景二:針對(duì)數(shù)據(jù)庫增刪查改函數(shù)的測(cè)試
1)被測(cè)試函數(shù)如下:
/**
*Message的數(shù)據(jù)訪問
*/
Class DMessage extends Dataaccess{
/**
*單條消息(通過緩存)
*/
Public static function get($message_id) {
$message=self==getCache($message_id);
If(!$message){
$message=self==getByDb($message_id);
If(self==isTure($message)) {
self==setCache($message);
}else{
Return$message;
}
}
Return$message;
}
2)測(cè)試函數(shù)如下:
Class DMessageTest extends CDbTestCase
{
Public$fixture=array(
‘message’=>’:tb_message’,
);
/**
*Implement testGet() {
*
*/
Public function testGet() {
$message=$this->message[‘sample1’]
DMessage==deleteCache($message[‘id’]);
$ret=DMessage==get($message[‘id’]);
$this->assertEquals($message[‘id’], $ret[‘id’]);
}
3)datafixture
Return array(
‘sample1’=>array(
‘id’=>1,
‘user_id’=>1,
‘content’=>’unit test’,
‘source’=>’unit test’,
‘lat’=>1,
‘lon’=>1,
Location’=>1,
‘forword_count’=>0,
‘reply_count’=>0,
‘pic_id’=>0,
‘pic_filename’=>’’,
‘pic_id_water’=>0,
‘pic_filename_water’=>’’,
‘created_time’=>’2011-03-21 11:21:59’,
‘last_forward’=>0
‘is_deleted’=>0,
‘fid’=>0,
‘is_safe’=>0’
‘media_json’=>’’
‘message_json’=>’’,
上面的DMessage class下的get函數(shù)是去獲取一條關(guān)于message的記錄。忽略此函數(shù)間的依賴性來說,如果在測(cè)試的時(shí)候,cache中不存在關(guān)于此message的記錄,則需要往數(shù)據(jù)庫中去取此條記錄,而在測(cè)試此函數(shù)的時(shí)刻,數(shù)據(jù)庫中是否存在需要查找的message記錄是無法確定的,所以會(huì)導(dǎo)致函數(shù)的上下文環(huán)境不確定,進(jìn)而導(dǎo)致測(cè)試無法進(jìn)行?;蛘咴诿看螠y(cè)試之前手動(dòng)地去刪除或者添加記錄,在測(cè)試過程中還要防止其他人刪除此記錄[5]。在測(cè)試函數(shù)中出現(xiàn)了fixtrue變量,這個(gè)變量的作用就是在每個(gè)測(cè)試method執(zhí)行之前清空數(shù)據(jù)庫中某張或者多張表里的數(shù)據(jù),然后插入給定的數(shù)據(jù),給定數(shù)據(jù)通過在fixture文件中設(shè)置,而fixture中文件命名規(guī)則為表名字.php。(例如數(shù)據(jù)中有一張表名字為tb_message,則fixture里有一個(gè)文件名字為tb_message.php,文件內(nèi)容對(duì)應(yīng)為一個(gè)數(shù)組,數(shù)組每個(gè)變量對(duì)應(yīng)數(shù)據(jù)庫表中一條記錄)。通過使用fixture,能夠使單元測(cè)試在一個(gè)給定的上下文環(huán)境中進(jìn)行[6]。
場(chǎng)景三:被測(cè)試的函數(shù)存在對(duì)其他函數(shù)調(diào)用
解決方案:1)使用phpunit自帶的mock或者stub方法2)使用 runkit中的 method_redifine 方法()。
1)被測(cè)試class
Class LContactNsg
{
/**
*@param$userId
*@param$sendUserId
*@return unknown_type
*/
Public static function agree($userId,$sendUserId)
{
If(DContactMsg==check($userId,$sendUserId))
{
DContactMsg==delete(array($userId,$sendUserId));
DContactMsg==delete(array($sendUserId, $userId));
If (false!==DContacts==insert(array($userId,$sendUserId)))
return true;
Else
return false;
}
return true;
}
}
2)測(cè)試 class
require_once’/home/work/htdocs/php/development/liuxiang/talk/dataaccess/DContactMsg.php’;
require_once’CsvFileIterator.php;
/**
*Test class for LContactMsg.
*Generated by PHPUnit on 2011-05-06 at 16:20:13.
*/
Class LContactMsgTest extend CTestCase
{
/**
*Implement testAgree().
*@dataaprovider agreeProvider
*/
Public function testAgree ($userId,$sendUserId,$expect,$re1,$re2,$re3){
runkit_method_redefine (‘DContactMsg’,’check’,’’,
“$re1,
RUNKIT_ACC_PUBLIC);
runkit_method_redefine(‘DContactMsg’,’delete’,’’,
“$re2,
RUNKIT_ACC_PUBLIC);
runkit_method_redefine(‘DContactMsg’,’insert’,’’,
“$re3,
RUNKIT_ACC_PUBLIC);
$result=LContactMsg==agree ($userId, $sendUserId,$expect,$re1,$re2,$re3);
$this->assertEquals($result,$expect);
}
Public function agreeProvider(0
{ return array(
array (1,8,true,’return true;’,’ return true;’’return true;’)
);
}
}
?>
由于單元測(cè)試關(guān)注點(diǎn)為當(dāng)前測(cè)試函數(shù)是否能夠能正確地完成相應(yīng)的任務(wù),而不關(guān)注被此函數(shù)調(diào)用函數(shù)能否正確完成任務(wù)。而如果不對(duì)調(diào)用函數(shù)進(jìn)行mock,當(dāng)此函數(shù)測(cè)試失敗時(shí),我們便無法立刻區(qū)分是當(dāng)前被測(cè)試函數(shù)出現(xiàn)bug還是被被測(cè)函數(shù)調(diào)用函數(shù)出現(xiàn)bug。因此我們可以mock被被測(cè)函數(shù)調(diào)用的函數(shù),讓其返回我們所期望的值,這樣就可以方便快捷地測(cè)試被測(cè)函數(shù)是否滿足要求[7]。此測(cè)試class中使用的是runkit函數(shù)庫中的runkit_method_redifine方法。而phpunit中也有相應(yīng)的處理方法,及mock和stub。但是phpunit中的方法不能處理static方法調(diào)用,而runkit無此限制[8]。
自動(dòng)化測(cè)試的目的是減少代碼的bug,一旦你開始習(xí)慣使用自動(dòng)化測(cè)試,你將發(fā)現(xiàn)你的代碼的bug在減少,你的代碼的可信性在增加,有了可信的保證,你可以對(duì)你的代碼進(jìn)行大膽的重構(gòu),取得事倍功半的效果[9]。
[1]吳高峽,王芙蓉.單元測(cè)試的自動(dòng)化實(shí)踐[J].計(jì)算機(jī)與數(shù)字工程,2007(1):15-17.WU Gao-xia,WANG Fu-rong.Unit test automation practices[J].Computer and Digital engineering,2007(1):15-17.
[2]陳靜.單元測(cè)試在軟件開發(fā)過程中的作用[J].艦船電子對(duì)抗,2006(3):25-28.CHEN Jing.Role of unit testing in software development[J].Warship EW.,2006(3):25-28.
[3]陳站華.軟件單元測(cè)試[J].無線電通信技術(shù),2003(5):124-126.CHEN Zhan-hua.Software unit test[J].Radio Communications Technologies,2003(5):124-126.
[4]侯鯤,林和平,楊威.設(shè)計(jì)模式在自動(dòng)單元測(cè)試框架中的應(yīng)用[J].計(jì)算機(jī)工程與應(yīng)用,2004(31):256-259.HOU Kun,LIN He-ping,YANG Wei.Application of design pattern in automated unit testing frameworks[J].Computer Engineering and Applications,2004(31):256-259.
[5]林海,歐鋼,向?yàn)?軟件測(cè)試策略綜述[J].軟件導(dǎo)刊,2008(10):165-168.LIN Hai,OU Gang,XIANG Wei.Overview of software testing strategies[J].Software DVD Guide,2008(10):165-168.
[6]張巍,尹海波,孫立財(cái).軟件的單元測(cè)試方法[J].光電技術(shù)應(yīng)用,2006(2):58-61.ZHANG Wei,YIN Hai-bo,SUN Li-cai.Software unit test method[J].Application of Opto-electronic Technology,2006(2):58-61.
[7]王麗達(dá).論軟件系統(tǒng)的測(cè)試[J].經(jīng)濟(jì)研究導(dǎo)刊,2011(14):82-85.WANG Li-da.On testing of software systems[J].Economic Research Guide Magazine,2011(14):82-85.
[8]許學(xué)軍.軟件測(cè)試軟環(huán)境的構(gòu)建與優(yōu)化[J].中國民航飛行學(xué)院學(xué)報(bào),2006(4):36-38.XU Xue-jun.Soft environment construction and optimization of software testing[J].Journal of China Civil Aviation Flying College,2006(4):36-38.
[9]王鵬,習(xí)媛媛,馬麗.單元測(cè)試在軟件質(zhì)量保證中的應(yīng)用研究[J].山西財(cái)經(jīng)大學(xué)學(xué)報(bào),2009(S2):52-54.WANG Peng,XI Yuan-yuan,Ma Li.Study on the application of unit testing in software quality assurance[J].Journal of Shanxi University of Finance and Economics,2009(S2):52-54.