測試也要設計—phpunit實踐

技術小美發表於2017-11-11

概述
本文闡述如何利用物件導向的思想,在phpunit框架下實現測試用例、資料檔案、配置資訊和lib庫等資訊分離,並能有效組合。

也許有些QA認為,測試程式碼只要能滿足測試要求即可,根本不需要有什麼設計的理念。其實不然,好的測試程式碼,應該是可讀性強,可擴充套件性強。以下分享一個我在實際專案中的小想法來闡述這個觀點,僅作拋磚引玉之用。
具體實現
在autoFunc測試目錄下,建立conf、data、lib三個目錄,分別用於儲存配置資訊、資料檔案和lib庫,測試用例直接放在autoFunc下。

 


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

 



弊端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
Method=$method
Bucket=$this->BUCKET
Object=$object
“;

if($this->IP != “”){

$content .= “Ip=”.$this->IP.”
“;

}

if($this->TIME != “”){ 

$content .= “Time=”.$this->TIME.”
“;

}

$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的測試程式碼同樣也需要。

測試也需要設計。

(作者:zhouxiuhu)

 

本文轉自百度技術51CTO部落格,原文連結:http://blog.51cto.com/baidutech/743445,如需轉載請自行聯絡原作者


相關文章