概述
本文阐述如何利用面向对象的思想,在phpunit框架下实现测试用例、数据文件、配置信息和lib库等信息分离,并能有效组合。
也许有些QA认为,测试代码只要能知足测试要求便可,根本不须要有什么设计的理念。其实否则,好的测试代码,应该是可读性强,可扩展性强。如下分享一个我在实际项目中的小想法来阐述这个观点,仅做抛砖引玉之用。
具体实现
在autoFunc测试目录下,建立conf、data、lib三个目录,分别用于保存配置信息、数据文件和lib库,测试用例直接放在autoFunc下。
php
A 方案
直接在test_object_put_get.php中require config.php和util.php,以下:
require_once 'conf/config.php';
require_once 'lib/util.php';
彷佛很简单,但这里却有两个问题,phpunit的测试用例,是以class继承PHPUnit_Framework_Testcase类的方式来组织的,这样在testcase中就没法访问config.php的变量列表,或许你会说,这个很好解决呀,直接的testcase的class中指定config.php的变量名为global类型便可,以下:
html
弊端1:config.php中有几个配置,testcase中就须要global几个,难免太手工了。
弊端2:util.php中function没法经过global方式声明,在testcase中也就没法访问到。
B 方案
鉴于A 方案的局限性,我提出了面向对象的方式,来实现配置信息、测试用例、lib库有效的隔离。B 方案最突出的地方在于引入了继承的概念,容我一一道来。
config.php
测试用例中总有一些常量会被反复使用到,使用配置文件的方式是全部测试人员的共识,在这里,我作了一个小小改善,将config信息包装为类的方式,以便于在lib库的类中使用到,以下:
class Config{
public $WEB_SITE = "http://test.com:8090/";
public $BUCKET = "vincent";
public $ACCESS_KEY = "jhPLaYVh11wo";
public $SECURE_KEY = "A23mtEjHwv1z";
public $SIGN_FLAG="TST";
public $DATA_DIR = "./data/";
//no use now
public $IP = "";
public $TIME = "";
}
util.php
编写测试用例时,总会有一些逻辑处理片断反复的出现,这形成了测试代码的大量冗余,也加大了维护成功,将这些逻辑处理封装为一个个函数是第一个步,以后将通用的函数抽取为lib库的形式,而那些函数适合抽取为lib库。根据经验我列举几个lib的共有特性:
1.与测试用例逻辑无关
2.完成单一职责
3.可被其余用例共用
若是知足这三个条件,那这个函数就能够抽取为lib库,以下文的signature签名函数。
在实际项目中,我抽取了部分函数为lib库,并将lib库也封装为类的形式,同时继承于class Config,以下:
require_once 'conf/config.php';
class Util extends Config{
/**
* 返回签名串
* @param string $method
* @param string $object
*/
public function signature($method, $object){
$content = "$this->SIGN_FLAG\nMethod=$method\nBucket=$this->BUCKET\nObject=$object\n";
if($this->IP != ""){
$content .= "Ip=".$this->IP."\n";
}
if($this->TIME != ""){
$content .= "Time=".$this->TIME."\n";
}
$sign = "?Sign=$this->SIGN_FLAG:$this->ACCESS_KEY";
return $sign;
}
}
注意:这里Util类继承于Config类,也就继承了Config类中的全部成员变量,故在Util类中能够直接经过$this指针直接访问到配置信息。
TEST CASES
testcases中经过在setUp()函数中new一个Util对象,这样就能够轻松使用lib库中全部方法了,以下:
require_once 'PHPUnit/Framework.php';
require_once 'lib/util.php';框架
class ObjectPutGET extends PHPUnit_Framework_Testcase{
protected $util;
protected function setUp(){
$this->util = new Util();
}
public function testNormal(){
$object = '/normalObj';
$sign = $this->util->signature("PUT", $object);
$url = $this->util->WEB_SITE.$this->util->BUCKET.$object.$sign;
$http = curl_init();
$infile = fopen("data/file1", "r");
curl_setopt($http, CURLOPT_URL, $url);
curl_setopt($http, CURLOPT_INFILE, $infile);
curl_setopt($http, CURLOPT_INFILESIZE, 8);
curl_setopt($http, CURLOPT_UPLOAD, 1);
curl_exec($http);
curl_close($http);
fclose($infile);
}
}
这里testcases中有一个protect的成员变量$util,并在setUp()中初始化,这样在每一个testcase中均可以使用$this->util来访问Util类中的全部方法和变量了。
问题:测试中可能遇到这样的问题,lib库的function依赖于config中的配置,在测试用例中调用function时,又但愿能用不一样的参数。
解决:按照gtest的测试经验,须要为function提供额外的参数,供传入不一样的值。既然使用面向对象了,这里就简单了,只须要经过实例化的lib库调用$this->util->配置项,直接更改配置项信息。若是但愿封装好点,能够设置get、set方法分别用于配置项的get和set。
数据驱动
将测试数据保存到data/目录下的相应文件中,经过php unit的dataprovider机制与测试代码结合,将测试数据与用例逻辑解耦合,增长case只须要相应增减数据文件,不须要变动用例逻辑,下降维护成本,提升可扩展性。
/**
* dataprovider for testObjectFileType
*/
public function fileType(){
return array(
array("fputtype.txt", "/putfiletype.txt"),
array("fputtype.docx", "/putfiletype.docx"),
array("fputtype.pdf", "/putfiletype.pdf"),
array("fputtype.xls", "/putfiletype.xls"),
array("fputtype.mp3", "/putfiletype.mp3"),
array("fputtype.mkv", "/putfiletype.mkv"),
array("fputtype.rar", "/putfiletype.rar")
);
}
/**
* 文件,文件类型为txt word excel pdf mp3 mkv rar
* @dataProvider fileType
*/
public function testObjectFileType($fileType, $object_name){
$fileName = $this->util->DATA_DIR.$fileType;
$obj = $object_name;
//put object
$result = $this->util->putObject($fileName, $obj);
$this->assertEquals("", $result);
//get object
$result = $this->util->getObject($obj);
//check
$expect = md5(file_get_contents($fileName));
$actual = md5($result);
$header = new Header(file_get_contents($this->util->HEADER_FILE));
$etag = $header->getETag();
$this->assertEquals($expect, $actual);
$this->assertEquals($expect, $etag);
}
这样组织后,测试用例、配置信息、数据文件以及lib库就解耦了,无论修改哪部分,均可以直接找到并修改,不用担忧会对其余case形成什么影响。
A. 编写测试用例,在测试用例根目录下找到对应测试文件,增减相应的case逻辑便可,而且能够在测试用例中轻松调用lib库,动态修改配置信息。
B. 修改数据文件?两步便可,在data目录下增减数据文件,修改对应测试用例的数据驱动信息。
C. 在conf目录中修改配置信息,因为配置信息是全局的,修改已有配置信息须要慎重。
D. lib库与conf同样是全局可见的,修改已有function须要考量对其余case有没有影响。
总结
不只仅RD的代码须要可扩展性,QA的测试代码一样也须要。
测试也须要设计。curl
(做者:zhouxiuhu)ide