王莉军
(渤海大学 大学计算机教研部,辽宁 锦州 121013)
一个函数、一个类编写完成,到底能不能正确工作?怎么测试它?PHP单元测试是个好办法,它提供了自动化测试的方法,使敏捷开发的自动化测试成为可能。
1)代码具备基本可测试性。及要求被测试函数具备输入输出。(本测试方案未考虑无输入输出函数的测试)
2)被测函数尽可能分情况说明输入输出。及期望输入及输出和非期望输入对应输出。
3)被测还是应该有基本的函数说明,表明函数的功能[1]。
1)对于某个系统,不同层的代码放置于不同文件夹下。以talk为例,其有dataaccess层和logic层,那么其dataaccess层代码放置于文件夹dataaccess之下。而单元测试文件的布局则和系统代码布局一一对应。对于某个文件a.php,其对应的测试文件命名则为aTest.php。而对于a.php中某个函数method来说,其对应的测试函数命名应该为testMethod[2]。
2)每个测试函数应该包括一定的注释。不依赖于dataprovider的情况。
/**
*@author****
*@note****
*@expect input**
*@expect output**
*@unexpect input**
*@unexpect output**
*/
依赖于dataprovider的情况:
/**
*@author**** /**
*@note**** *@expect 1,2,3
*@dataprovider** *@unexpect 4,5,6
*/ */
1)在测试根目录下应该包含有各文件夹下文件测试覆盖率统计文件夹。
2)单元测试代码应该避免过多的依赖关系。尽量减少对外部环境依赖,减少对外部代码具体实现依赖,减少对测试内部函数之间的依赖[3]。
场景一:一般简单情况的函数测试
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函数的测试函数testAdd以外,还包含setUp和tearDown函数。其中setUp是在每个测试函数执行之前都会自动执行一遍,用来自动建立每个method的独立测试上下文环境,通用tearDown在每个测试函数执行之后执行一遍,用来清除此method执行之中设定的上下文[4]。而testAdd则用来对add函数进行测试。testAdd函数中只包含一条语句,这条语句即假定通过调用add函数执行1加2,我们期望其返回的结果与3相等。如果相等,执行结果则通过,如果不相等则测试失败,说明代码并没有完成我们想要的功能,如图1所示。
图1 测试结果Fig.1 Test execution results
场景二:针对数据库增删查改函数的测试
1)被测试函数如下:
/**
*Message的数据访问
*/
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)测试函数如下:
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函数是去获取一条关于message的记录。忽略此函数间的依赖性来说,如果在测试的时候,cache中不存在关于此message的记录,则需要往数据库中去取此条记录,而在测试此函数的时刻,数据库中是否存在需要查找的message记录是无法确定的,所以会导致函数的上下文环境不确定,进而导致测试无法进行。或者在每次测试之前手动地去删除或者添加记录,在测试过程中还要防止其他人删除此记录[5]。在测试函数中出现了fixtrue变量,这个变量的作用就是在每个测试method执行之前清空数据库中某张或者多张表里的数据,然后插入给定的数据,给定数据通过在fixture文件中设置,而fixture中文件命名规则为表名字.php。(例如数据中有一张表名字为tb_message,则fixture里有一个文件名字为tb_message.php,文件内容对应为一个数组,数组每个变量对应数据库表中一条记录)。通过使用fixture,能够使单元测试在一个给定的上下文环境中进行[6]。
场景三:被测试的函数存在对其他函数调用
解决方案: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;’)
);
}
}
?>
由于单元测试关注点为当前测试函数是否能够能正确地完成相应的任务,而不关注被此函数调用函数能否正确完成任务。而如果不对调用函数进行mock,当此函数测试失败时,我们便无法立刻区分是当前被测试函数出现bug还是被被测函数调用函数出现bug。因此我们可以mock被被测函数调用的函数,让其返回我们所期望的值,这样就可以方便快捷地测试被测函数是否满足要求[7]。此测试class中使用的是runkit函数库中的runkit_method_redifine方法。而phpunit中也有相应的处理方法,及mock和stub。但是phpunit中的方法不能处理static方法调用,而runkit无此限制[8]。
自动化测试的目的是减少代码的bug,一旦你开始习惯使用自动化测试,你将发现你的代码的bug在减少,你的代码的可信性在增加,有了可信的保证,你可以对你的代码进行大胆的重构,取得事倍功半的效果[9]。
[1]吴高峡,王芙蓉.单元测试的自动化实践[J].计算机与数字工程,2007(1):15-17.WU Gao-xia,WANG Fu-rong.Unit test automation practices[J].Computer and Digital engineering,2007(1):15-17.
[2]陈静.单元测试在软件开发过程中的作用[J].舰船电子对抗,2006(3):25-28.CHEN Jing.Role of unit testing in software development[J].Warship EW.,2006(3):25-28.
[3]陈站华.软件单元测试[J].无线电通信技术,2003(5):124-126.CHEN Zhan-hua.Software unit test[J].Radio Communications Technologies,2003(5):124-126.
[4]侯鲲,林和平,杨威.设计模式在自动单元测试框架中的应用[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].光电技术应用,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]王丽达.论软件系统的测试[J].经济研究导刊,2011(14):82-85.WANG Li-da.On testing of software systems[J].Economic Research Guide Magazine,2011(14):82-85.
[8]许学军.软件测试软环境的构建与优化[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]王鹏,习媛媛,马丽.单元测试在软件质量保证中的应用研究[J].山西财经大学学报,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.