背景
PHPUnit 是一個(gè)面向PHP開發(fā)者的測(cè)試框架胸完,可以寫提供編程代碼質(zhì)量,確保項(xiàng)目可以持續(xù)維護(hù)
安裝phpunit
項(xiàng)目不采用全局安裝 翘贮,我們使用composer安裝phpunit
composer require --dev phpunit/phpunit 5.7.27
備注:支持PHP5.6版本
創(chuàng)建配置文件
phpunit.xml放置在項(xiàng)目的根目錄中赊窥,是phpunit默認(rèn)讀取的配置文件,實(shí)現(xiàn)配置執(zhí)行單元測(cè)試執(zhí)行的初始化文件狸页,測(cè)試套件目錄
默認(rèn)情況下锨能,你應(yīng)用程序的 tests 目錄下包含兩個(gè)子目錄:Feature 和 Unit。
單元測(cè)試(Unit)是針對(duì)你的代碼中非常少芍耘,而且相對(duì)獨(dú)立的一部分代碼來進(jìn)行的測(cè)試址遇。實(shí)際上,大部分單元測(cè)試都是針對(duì)單個(gè)方法進(jìn)行的斋竞。
功能測(cè)試(Feature)能測(cè)試你的大部分代碼倔约,包括多個(gè)對(duì)象如何相互交互,甚至是對(duì) JSON 端點(diǎn)的完整 HTTP 請(qǐng)求窃页。 通常跺株,你的大多數(shù)測(cè)試應(yīng)該是功能測(cè)試。這些類型的測(cè)試可以最大程度地確保你的系統(tǒng)作為一個(gè)整體按預(yù)期運(yùn)行脖卖。
配置自動(dòng)加載
我們?cè)赾omposer.json添加autoload規(guī)則乒省,使用psr-4的自動(dòng)加載類
"require-dev": {
"phpunit/phpunit": "5.7.27"
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
}
執(zhí)行類自動(dòng)加載
composer dump-autoload --optimize
執(zhí)行單元測(cè)試
./vendor/bin/phpunit -c ./phpunit.xml
只允許以下指定用例
./vendor/bin/phpunit --filter OrderVehicleNewTest --debug
官方手冊(cè)
應(yīng)用到項(xiàng)目中實(shí)踐
配置phpunit.xml
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="./protect/__init__.php"
colors="true"
stopOnFailure="true"
verbose="true"
>
<testsuites>
<testsuite name="Unit">
<directory suffix="Test.php">./tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory suffix="Test.php">./tests/Feature</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./protect</directory>
</whitelist>
</filter>
<php> <!--https://www.kancloud.cn/manual/phpunit-book/68752-->
<server name="APP_ENV" value="testing"/>
</php>
</phpunit>
執(zhí)行腳本
<?php
require_once dirname(__FILE__) . '/../__init__.php';
class Script_Phpunit extends \PHPUnit_TextUI_Command{
public static function main($exit = true){
$command = new static;
array_pop($_SERVER['argv']);
return $command->run($_SERVER['argv'], $exit);
}
}
//控制環(huán)境執(zhí)行
if(in_array(HLL_ENV,['dev','stg','pre_1'])){
(new Script_Phpunit)->main();
}
應(yīng)用基礎(chǔ)類
<?php
namespace Tests;
use PHPUnit\Framework\TestCase;
abstract class TestBaseCase extends TestCase
{
protected $commit = false; //是否提交事務(wù)
/**
* @notes: 測(cè)試用例類的第一個(gè)測(cè)試之前
* @author: jackin.chen
* @time: 2022/10/20 16:57
* @function: setUpBeforeClass
*/
public static function setUpBeforeClass()
{
}
/**
* @notes: 在運(yùn)行每個(gè)測(cè)試方法前自動(dòng)調(diào)用
* @author: jackin.chen
* @time: 2022/10/10 10:33
* @function: setUp
*/
protected function setUp()
{
//見:https://github.com/sebastianbergmann/phpunit/issues/1598
if (!empty(\PHPUnit_Util_Blacklist::$blacklistedClassNames)) {
foreach (\PHPUnit_Util_Blacklist::$blacklistedClassNames as $className => $parent) {
try {
if (!class_exists($className)) {
unset(\PHPUnit_Util_Blacklist::$blacklistedClassNames[$className]);
}
} catch (\Exception $e) {
unset(\PHPUnit_Util_Blacklist::$blacklistedClassNames[$className]);
}
}
}
//在配置xml直接配置啟動(dòng)文件
//require_once (__DIR__ .'/../protect/__init__.php');
}
/**
* @notes: 會(huì)在每個(gè)測(cè)試方法允許后被調(diào)用
* @author: jackin.chen
* @time: 2022/10/16 14:49
* @function: tearDown
*/
public function tearDown()
{
}
/**
* @notes: 對(duì)比JSON驗(yàn)證數(shù)據(jù)
* @param $response
* @param $expected
* @param string $msg
* @return $this
* @author: jackin.chen
* @time: 2022/10/20 13:49
* @function: assertJsonString
*/
protected function assertJsonString($response,$expected,$msg = 'msg'){
$this->assertNotEmpty($response);
$this->assertNotEmpty($expected);
$this->assertArrayHasKey($msg,$response);
$expect = $decoded = [];
foreach ($expected as $key => $value){
if(isset($response[$key])){
$expect[$key] = $value; // 期望值
$decoded[$key] = $response[$key]; //返回值
}
}
$message = isset($response[$msg]) ? $response[$msg] : '';
$expectedJson = json_encode($expect, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
$actualJson = json_encode($decoded, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
$this->assertJsonStringEqualsJsonString($expectedJson,$actualJson,$message);
return $this;
}
/**
* @notes:
* @param string $uri 路由地址
* @param mixed $parameters 參數(shù)
* @return false|mixed
* @author: jackin.chen
* @time: 2022/10/10 10:21
* @function: call
*/
protected function call($uri,$parameters){
$uri = self::prepareUrlForRequest($uri);
list($class,$action) = explode('/',$uri);
$_GET['_m'] = $class;
$_GET['_a'] = $action;
$controller = 'Ctrller_'.ucfirst($class);
ob_start();
$instance = new $controller;
$result = call_user_func_array([$instance, $action], [$parameters]);
if(!is_array($result)){
$json = ob_get_contents();
$result = \Lib_Func::jsonDecode($json);
}
ob_end_clean();
return $result;
}
/**
* @notes: 下劃線轉(zhuǎn)大駝峰
* @param string $words
* @param string $separator
* @return string
* @author: jackin.chen
* @time: 2022/10/10 10:16
* @function: prepareUrlForRequest
*/
protected function prepareUrlForRequest($words,$separator='_')
{
$words = $separator. str_replace($separator, " ", strtolower($words));
return ltrim(str_replace(" ", "", ucwords($words)), $separator );
}
/**
* @notes: 使用事務(wù)調(diào)試接口避免臟數(shù)據(jù)
* @param \Closure $callback
* @param array $parameters
* @return false|mixed
* @author: jackin.chen
* @time: 2022/10/10 11:18
* @function: transaction
*/
protected function transaction(\Closure $callback,$parameters=[]){
$model_common = new \Model_Common();
$model_common->TransStart();
$result = call_user_func($callback,$parameters); //執(zhí)行回調(diào)函數(shù)
if($this->commit){
$model_common->Commit();
}else{
$model_common->RollBack();
}
return $result;
}
}
應(yīng)用業(yè)務(wù)類
<?php
namespace Tests\Feature;
use Tests\TestBaseCase;
use Tests\Data\OrderVehicleJson;
class OrderVehicleNewTest extends TestBaseCase
{
protected $commit = false;
protected $ret = 0;
/**
* @notes: 業(yè)務(wù)線數(shù)據(jù)
* @author: jackin.chen
* @time: 2022/11/8 16:35
* @function: testBusinessList
*/
public function testBusinessList()
{
$response = $this->call('order_vehicle_new/business',[]);
$this->assertJsonString($response,['ret' => $this->ret]);
$this->assertNotEmpty($response['data']);
}
/**
* @notes: 查詢數(shù)據(jù)供給器
* @return \int[][]
* @author: jackin.chen
* @time: 2022/10/20 14:17
* @function: additionProvider
*/
public function vehicleStandardProvider()
{
//類型、國(guó)標(biāo)ID畦木、期望值
return array(
array(\Input_OrderVehicleNew::IS_STATUS1, \Input_OrderVehicleNew::VEHICLE_ATTR0,$this->ret),
array(\Input_OrderVehicleNew::IS_STATUS1, \Input_OrderVehicleNew::VEHICLE_ATTR1,$this->ret),
array(\Input_OrderVehicleNew::IS_STATUS2, \Input_OrderVehicleNew::VEHICLE_ATTR0,$this->ret),
array(\Input_OrderVehicleNew::IS_STATUS2, \Input_OrderVehicleNew::VEHICLE_ATTR1,$this->ret)
);
}
/**
* @notes: 所屬國(guó)標(biāo)訂單車型
* @param $vehicle_attr_type
* @param $status
* @param $expected
* @author: jackin.chen
* @time: 2022/11/8 16:41
* @function: testVehicleStandardList
* @dataProvider vehicleStandardProvider
*/
public function testVehicleStandardList($vehicle_attr_type,$status,$expected)
{
$params = [
'vehicle_attr_type'=>$vehicle_attr_type,
'status'=>$status
];
$response = $this->call('order_vehicle_standard/a_get_list_all',$params);
$this->assertJsonString($response,['ret' => $expected]);
$this->assertNotEmpty($response['data']);
}
/**
* @notes: 獲取關(guān)聯(lián)國(guó)標(biāo)車型的圖片信息二級(jí)下拉框信息
* @author: jackin.chen
* @time: 2022/11/9 11:05
* @function: testGetImgOption
*/
public function testGetImgOption()
{
$params = [];
$response = $this->call('order_vehicle_standard/a_get_img_option',$params);
$this->assertJsonString($response,['ret' => $this->ret]);
$this->assertNotEmpty($response['data']);
}
/**
* @notes:
* @param $params
* @author: jackin.chen
* @time: 2022/11/9 13:53
* @function: testOrderVehicleNewAdd
*/
public function testOrderVehicleNewAdd()
{
$json = file_get_contents(__DIR__.'/../Data/add_order_vehicle.json');
$params = json_decode($json,true);
$response = $this->call('order_vehicle_new/add_order_vehicle',$params);
$this->assertJsonString($response,['ret' => $this->ret]);
$this->assertNotEmpty($response['data']);
}
}