使用PHPunit

背景

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']);
    }


}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容