php逻辑测试,测试也要设计—phpunit实践

概述本文阐述如何利用面向对象的思想,在phpunit框架下实现测试用例、数据文件、配置信息和lib库等信息分离,并能有效组合。

也许有些QA认为,测试代码只要能满足测试要求即可,根本不需要有什么设计的理念。其实不然,好的测试代码,应该是可读性强,可扩展性强。以下分享一个我在实际项目中的小想法来阐述这个观点,仅作抛砖引玉之用。

具体实现在autoFunc测试目录下,创建conf、data、lib三个目录,分别用于保存配置信息、数据文件和lib库,测试用例直接放在autoFunc下。

0795c09576b7458e5dda3f0594116905.png

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类型即可,如下:

7aa835178382461379735e3cbe6b2aa3.png

弊端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 CASEStestcases中通过在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的测试代码同样也需要。

测试也需要设计。

(作者:zhouxiuhu)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值