王莉軍
(渤海大學 大學計算機教研部,遼寧 錦州 121013)
一個函數(shù)、一個類編寫完成,到底能不能正確工作?怎么測試它?PHP單元測試是個好辦法,它提供了自動化測試的方法,使敏捷開發(fā)的自動化測試成為可能。
1)代碼具備基本可測試性。及要求被測試函數(shù)具備輸入輸出。(本測試方案未考慮無輸入輸出函數(shù)的測試)
2)被測函數(shù)盡可能分情況說明輸入輸出。及期望輸入及輸出和非期望輸入對應輸出。
3)被測還是應該有基本的函數(shù)說明,表明函數(shù)的功能[1]。
1)對于某個系統(tǒng),不同層的代碼放置于不同文件夾下。以talk為例,其有dataaccess層和logic層,那么其dataaccess層代碼放置于文件夾dataaccess之下。而單元測試文件的布局則和系統(tǒng)代碼布局一一對應。對于某個文件a.php,其對應的測試文件命名則為aTest.php。而對于a.php中某個函數(shù)method來說,其對應的測試函數(shù)命名應該為testMethod[2]。
2)每個測試函數(shù)應該包括一定的注釋。不依賴于dataprovider的情況。
/**
*@author****
*@note****
*@expect input**
*@expect output**
*@unexpect input**
*@unexpect output**
*/
依賴于dataprovider的情況:
/**
*@author**** /**
*@note**** *@expect 1,2,3
*@dataprovider** *@unexpect 4,5,6
*/ */
1)在測試根目錄下應該包含有各文件夾下文件測試覆蓋率統(tǒng)計文件夾。
2)單元測試代碼應該避免過多的依賴關(guān)系。盡量減少對外部環(huán)境依賴,減少對外部代碼具體實現(xiàn)依賴,減少對測試內(nèi)部函數(shù)之間的依賴[3]。
場景一:一般簡單情況的函數(shù)測試
1)被測試class如下:
Class MyMathClass
{
/*
**add two given values,and return the sun
*/
Public function add($a,$b)
{
Return$a+$b;
}
}
?>
2)測試 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));
}
}
?>
簡單單元測試class里僅僅包含一個被測試的method的,而在生成的測試class里邊包含了除對應add函數(shù)的測試函數(shù)testAdd以外,還包含setUp和tearDown函數(shù)。其中setUp是在每個測試函數(shù)執(zhí)行之前都會自動執(zhí)行一遍,用來自動建立每個method的獨立測試上下文環(huán)境,通用tearDown在每個測試函數(shù)執(zhí)行之后執(zhí)行一遍,用來清除此method執(zhí)行之中設(shè)定的上下文[4]。而testAdd則用來對add函數(shù)進行測試。testAdd函數(shù)中只包含一條語句,這條語句即假定通過調(diào)用add函數(shù)執(zhí)行1加2,我們期望其返回的結(jié)果與3相等。如果相等,執(zhí)行結(jié)果則通過,如果不相等則測試失敗,說明代碼并沒有完成我們想要的功能,如圖1所示。
圖1 測試結(jié)果Fig.1 Test execution results
場景二:針對數(shù)據(jù)庫增刪查改函數(shù)的測試
1)被測試函數(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)測試函數(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ù)間的依賴性來說,如果在測試的時候,cache中不存在關(guān)于此message的記錄,則需要往數(shù)據(jù)庫中去取此條記錄,而在測試此函數(shù)的時刻,數(shù)據(jù)庫中是否存在需要查找的message記錄是無法確定的,所以會導致函數(shù)的上下文環(huán)境不確定,進而導致測試無法進行?;蛘咴诿看螠y試之前手動地去刪除或者添加記錄,在測試過程中還要防止其他人刪除此記錄[5]。在測試函數(shù)中出現(xiàn)了fixtrue變量,這個變量的作用就是在每個測試method執(zhí)行之前清空數(shù)據(jù)庫中某張或者多張表里的數(shù)據(jù),然后插入給定的數(shù)據(jù),給定數(shù)據(jù)通過在fixture文件中設(shè)置,而fixture中文件命名規(guī)則為表名字.php。(例如數(shù)據(jù)中有一張表名字為tb_message,則fixture里有一個文件名字為tb_message.php,文件內(nèi)容對應為一個數(shù)組,數(shù)組每個變量對應數(shù)據(jù)庫表中一條記錄)。通過使用fixture,能夠使單元測試在一個給定的上下文環(huán)境中進行[6]。
場景三:被測試的函數(shù)存在對其他函數(shù)調(diào)用
解決方案:1)使用phpunit自帶的mock或者stub方法2)使用 runkit中的 method_redifine 方法()。
1)被測試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)測試 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;’)
);
}
}
?>
由于單元測試關(guān)注點為當前測試函數(shù)是否能夠能正確地完成相應的任務,而不關(guān)注被此函數(shù)調(diào)用函數(shù)能否正確完成任務。而如果不對調(diào)用函數(shù)進行mock,當此函數(shù)測試失敗時,我們便無法立刻區(qū)分是當前被測試函數(shù)出現(xiàn)bug還是被被測函數(shù)調(diào)用函數(shù)出現(xiàn)bug。因此我們可以mock被被測函數(shù)調(diào)用的函數(shù),讓其返回我們所期望的值,這樣就可以方便快捷地測試被測函數(shù)是否滿足要求[7]。此測試class中使用的是runkit函數(shù)庫中的runkit_method_redifine方法。而phpunit中也有相應的處理方法,及mock和stub。但是phpunit中的方法不能處理static方法調(diào)用,而runkit無此限制[8]。
自動化測試的目的是減少代碼的bug,一旦你開始習慣使用自動化測試,你將發(fā)現(xiàn)你的代碼的bug在減少,你的代碼的可信性在增加,有了可信的保證,你可以對你的代碼進行大膽的重構(gòu),取得事倍功半的效果[9]。
[1]吳高峽,王芙蓉.單元測試的自動化實踐[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]陳靜.單元測試在軟件開發(fā)過程中的作用[J].艦船電子對抗,2006(3):25-28.CHEN Jing.Role of unit testing in software development[J].Warship EW.,2006(3):25-28.
[3]陳站華.軟件單元測試[J].無線電通信技術(shù),2003(5):124-126.CHEN Zhan-hua.Software unit test[J].Radio Communications Technologies,2003(5):124-126.
[4]侯鯤,林和平,楊威.設(shè)計模式在自動單元測試框架中的應用[J].計算機工程與應用,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]林海,歐鋼,向為.軟件測試策略綜述[J].軟件導刊,2008(10):165-168.LIN Hai,OU Gang,XIANG Wei.Overview of software testing strategies[J].Software DVD Guide,2008(10):165-168.
[6]張巍,尹海波,孫立財.軟件的單元測試方法[J].光電技術(shù)應用,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]王麗達.論軟件系統(tǒng)的測試[J].經(jīng)濟研究導刊,2011(14):82-85.WANG Li-da.On testing of software systems[J].Economic Research Guide Magazine,2011(14):82-85.
[8]許學軍.軟件測試軟環(huán)境的構(gòu)建與優(yōu)化[J].中國民航飛行學院學報,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]王鵬,習媛媛,馬麗.單元測試在軟件質(zhì)量保證中的應用研究[J].山西財經(jīng)大學學報,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.