背景
PHPUnit 是一个面向PHP开发者的测试框架,可以写提供编程代码质量,确保项目可以持续维护
安装phpunit
项目不采用全局安装 ,我们使用composer安装phpunit
composer require --dev phpunit/phpunit 5.7.27
备注:支持PHP5.6版本
创建配置文件
phpunit.xml放置在项目的根目录中,是phpunit默认读取的配置文件,实现配置执行单元测试执行的初始化文件,测试套件目录
默认情况下,你应用程序的 tests 目录下包含两个子目录:Feature 和 Unit。
单元测试(Unit)是针对你的代码中非常少,而且相对独立的一部分代码来进行的测试。实际上,大部分单元测试都是针对单个方法进行的。
功能测试(Feature)能测试你的大部分代码,包括多个对象如何相互交互,甚至是对 JSON 端点的完整 HTTP 请求。 通常,你的大多数测试应该是功能测试。这些类型的测试可以最大程度地确保你的系统作为一个整体按预期运行。
配置自动加载
我们在composer.json添加autoload规则,使用psr-4的自动加载类
"require-dev": {
"phpunit/phpunit": "5.7.27"
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
}
执行类自动加载
composer dump-autoload --optimize
执行单元测试
./vendor/bin/phpunit -c ./phpunit.xml
只允许以下指定用例
./vendor/bin/phpunit --filter OrderVehicleNewTest --debug
官方手册
应用到项目中实践
配置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>
执行脚本
<?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);
}
}
//控制环境执行
if(in_array(HLL_ENV,['dev','stg','pre_1'])){
(new Script_Phpunit)->main();
}
应用基础类
<?php
namespace Tests;
use PHPUnit\Framework\TestCase;
abstract class TestBaseCase extends TestCase
{
protected $commit = false; //是否提交事务
/**
* @notes: 测试用例类的第一个测试之前
* @author: jackin.chen
* @time: 2022/10/20 16:57
* @function: setUpBeforeClass
*/
public static function setUpBeforeClass()
{
}
/**
* @notes: 在运行每个测试方法前自动调用
* @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直接配置启动文件
//require_once (__DIR__ .'/../protect/__init__.php');
}
/**
* @notes: 会在每个测试方法允许后被调用
* @author: jackin.chen
* @time: 2022/10/16 14:49
* @function: tearDown
*/
public function tearDown()
{
}
/**
* @notes: 对比JSON验证数据
* @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 参数
* @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: 下划线转大驼峰
* @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: 使用事务调试接口避免脏数据
* @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); //执行回调函数
if($this->commit){
$model_common->Commit();
}else{
$model_common->RollBack();
}
return $result;
}
}
应用业务类
<?php
namespace Tests\Feature;
use Tests\TestBaseCase;
use Tests\Data\OrderVehicleJson;
class OrderVehicleNewTest extends TestBaseCase
{
protected $commit = false;
protected $ret = 0;
/**
* @notes: 业务线数据
* @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: 查询数据供给器
* @return \int[][]
* @author: jackin.chen
* @time: 2022/10/20 14:17
* @function: additionProvider
*/
public function vehicleStandardProvider()
{
//类型、国标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: 所属国标订单车型
* @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: 获取关联国标车型的图片信息二级下拉框信息
* @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']);
}
}