PHPUnit实践三(构建模块化的测试单元)

本系列教程所有的PHPUnit测试基于PHPUnit6.5.9版本,Lumen 5.5框架

目录结构

模块下的目录是符合Lumen的模块结构的
如:Controllers、Models、Logics等是Lumen模块目录下的结构目录
如果有自己的目录同级分配即可,如我这里的Requests

整体结构

├── BaseCase.php 重写过Lumen基类的测试基类,用于我们用这个基类做测试基类,后续会说明
├── bootstrap.php tests自动加载文件
├── Cases 测试用例目录
│   └── Headline 某测试模块
│       ├── logs 日志输出目录
│       ├── PipeTest.php PHPUnit流程测试用例
│       ├── phpunit.xml phpunit配置文件xml
│       └── README.md 本模块测试用例说明
├── ExampleTest.php 最原始测试demo
└── TestCase.php Lumen自带的测试基类

某模块的目录结构

Headline  //某测试模块测试用例目录
├── Cache
├── Controllers
│   ├── ArticleTest.php
│   ├── ...
├── Listeners
│   └── MyListener.php
├── Logics
├── Models
│   ├── ArticleTest.php
│   ├── ...
├── README.md
├── Requests
│   ├── ArticleTest.php
│   ├── ...
├── logs //日志和覆盖率目录
│   ├── html
│   │   ├── ...
│   │   └── index.html
│   ├── logfile.xml
│   ├── testdox.html
│   └── testdox.txt
├── phpunit-debug-demo.xml   //phpunit.xml案例
├── phpunit-debug.xml        //改名后测试用的
└── phpunit.xml              //正式用的xml配置

BaseCase.php

<?php
namespace Test;

use Illuminate\Database\Eloquent\Factory;

class BaseCase extends TestCase
{
    protected $seeder = false;

    const DOMAIN = "http://xxx.com";
    const API_URI = [];
    const TOKEN = [
        'local' => 'token*',
        'dev' => 'token*',
        'prod' => '' //如果测试真实请填写授权token
    ];

    /**
     * 重写setUp
     */
    public function setUp()
    {
        parent::setUp();

        $this->seeder = false;
        if (method_exists($this, 'factory')) {
            $this->app->make('db');
            $this->factory($this->app->make(Factory::class));

            if (method_exists($this, 'seeder')) {
                if (!method_exists($this, 'seederRollback')) {
                    dd("请先创建seederRollback回滚方法");
                }
                $this->seeder = true;
                $this->seeder();
            }
        }
    }

    /**
     * 重写tearDown
     */
    public function tearDown()
    {
        if ($this->seeder && method_exists($this, 'seederRollback')) {
            $this->seederRollback();
        }

        parent::tearDown();
    }

    /**
     * 获取地址
     * @param string $apiKey
     * @param string $token
     * @return string
     */
    protected function getRequestUri($apiKey = 'list', $token = 'dev', $ddinfoQuery = true)
    {
        $query = "?token=" . static::TOKEN[strtolower($token)];
        if ($ddinfoQuery) {
            $query = $query . "&" . http_build_query(static::DDINFO);
        }

        return $apiUri = static::DOMAIN . static::API_URI[$apiKey] . $query;
    }
}

phpunit-debug-demo.xml

本文件是我们单独为某些正在测试的测试用例,直接编写的xml,可以不用来回测试,已经测试成功的测试用例了,最后全部编写完测试用例,再用正式phpunit.xml即可,具体在运行测试阶段看如何指定配置

<?xml version="1.0" encoding="UTF-8"?>

<phpunit
        bootstrap="../../bootstrap.php"
        convertErrorsToExceptions="true"
        convertNoticesToExceptions="false"
        convertWarningsToExceptions="false"
        colors="true">
    <filter>
        <whitelist processuncoveredfilesfromwhitelist="true">
            <directory suffix=".php">../../../app/Http/Controllers/Headline</directory>
            <directory suffix=".php">../../../app/Http/Requests/Headline</directory>
            <directory suffix=".php">../../../app/Models/Headline</directory>
            <exclude><file>../../../app/Models/Headline/ArticleKeywordsRelationModel.php</file>
            </exclude>
        </whitelist>
    </filter>

    <testsuites>
        <testsuite name="Headline Test Suite">
            <directory>./</directory>
        </testsuite>
    </testsuites>

    <php>
        <ini name="date.timezone" value="PRC"/>
        <env name="APP_ENV" value="DEV"/>
    </php>

    <logging>
        <log type="coverage-html" target="logs/html/" lowUpperBound="35"
             highLowerBound="70"/>
        <log type="json" target="logs/logfile.json"/>
        <log type="tap" target="logs/logfile.tap"/>
        <log type="junit" target="logs/logfile.xml" logIncompleteSkipped="false"/>
        <log type="testdox-html" target="logs/testdox.html"/>
        <log type="testdox-text" target="logs/testdox.txt"/>
    </logging>

    <listeners>
        <!--<listener class="\Test\Cases\Headline\Listeners\MyListener" file="./Listeners/MyListener.php">-->
            <!--<arguments>-->
                <!--<array>-->
                    <!--<element key="0">-->
                        <!--<string>Sebastian</string>-->
                    <!--</element>-->
                <!--</array>-->
                <!--<integer>22</integer>-->
                <!--<string>April</string>-->
                <!--<double>19.78</double>-->
                <!--<null/>-->
                <!--<object class="stdClass"/>-->
            <!--</arguments>-->
        <!--</listener>-->
        <!--<listener class="\Test\Cases\Headline\Listeners\MyListener" file="./Listeners/MyListener.php">-->
            <!--<arguments>-->
                <!--<array>-->
                    <!--<element key="0">-->
                        <!--<string>Sebastian</string>-->
                    <!--</element>-->
                <!--</array>-->
                <!--<integer>22</integer>-->
            <!--</arguments>-->
        <!--</listener>-->
    </listeners>
</phpunit>

测试用例案例

<?php
/**
 * Created by PhpStorm.
 * User: qikailin
 * Date: 2019-01-29
 * Time: 11:57
 */

namespace Test\Cases\Headline\Articles;

use App\Http\Controllers\Headline\ArticleController;
use App\Models\Headline\ArticleCategoryRelationModel;
use App\Models\Headline\ArticleContentModel;
use App\Models\Headline\ArticleKeywordsRelationModel;
use App\Models\Headline\ArticlesModel;
use Faker\Generator;
use Illuminate\Http\Request;
use Test\BaseCase;

class ArticleTest extends BaseCase
{
    private static $model;

    public static function setUpBeforeClass()
    {
        parent::setUpBeforeClass();

        self::$model = new ArticlesModel();
    }

    /**
     * 生成factory faker 数据构建模型对象
     * @codeCoverageIgnore
     */
    public function factory($factory)
    {
        $words = ["测试", "文章", "模糊", "搜索"];
        $id = 262;
        $factory->define(ArticlesModel::class, function (Generator $faker) use (&$id, $words) {
            $id++;
            return [
                'id' => $id,
                'uri' => $faker->lexify('T???????????????????'),
                'title' => $id == 263 ? "搜索" : $words[rand(0, sizeof($words) - 1)],
                'authorId' => 1,
                'state' => 1,
                'isUpdated' => 0,
            ];
        });
    }

    /**
     * 生成模拟的数据,需seederRollback 成对出现
     */
    public function seeder()
    {
        $articles = factory(ArticlesModel::class, 10)->make();
        foreach ($articles as $article) { // 注意: article为引用对象,不是copy
            if ($article->isRecommend) {
                $article->recommendTime = time();
            }
            $article->save();
        }
    }

    /**
     * getArticleList 测试数据
     * @return array
     */
    public function getArticleListDataProvider()
    {
        return [
            [1, "搜索", 1, 10, 1],
            [2, "搜索", 1, 10, 0],
            [2, null, 1, 10, 0],
            [3, "搜索", 1, 10, 0],
            [1, null, 1, 10, 1],
            [2, null, 1, 10, 0],
            [3, null, 1, 10, 0],
        ];
    }

    /**
     * @dataProvider getArticleListDataProvider
     */
    public function testGetArticleList($type, $searchText, $page, $pageSize, $expceted)
    {
        $rst = self::$model->getArticleList($type, $searchText, $page, $pageSize);

        $this->assertGreaterThanOrEqual($expceted, sizeof($rst));

        $rst = self::$model->getArticleCount($type, $searchText);

        $this->assertGreaterThanOrEqual($expceted, $rst);
    }

    /**
     * addArticle 测试数据
     * @return array
     */
    public function addArticleDataProvider()
    {
        return [
            [
                [
                    'id' => 273,
                    'uri' => 'dddddddddd0123'
                ],
                'save',
                0
            ],
            [
                [
                    'id' => 274,
                    'uri' => 'dddddddddd123'
                ],
                'publish',
                0
            ],
            [
                [
                    'id' => 275,
                    'uri' => 'dddddddddd456'
                ],
                'preview',
                0
            ],
        ];
    }

    /**
     * @dataProvider addArticleDataProvider
     */
    public function testAdd($data, $action, $expected)
    {
        $rst = self::$model->addArticle($data, $action);

        if ($rst) {
            self::$model::where('id', $rst)->delete();
        }

        $this->assertGreaterThanOrEqual($expected, $rst);
    }

    public function testGetArticleInfo()
    {
        $rst = self::$model->getArticleInfo(263, 0);

        $this->assertGreaterThanOrEqual(1, sizeof($rst));

        $rst = self::$model->getArticleInfo(2000, 1);

        $this->assertEquals(0, sizeof($rst));
    }


    /**
     * 回滚模拟的数据到初始状态
     */
    public function seederRollback()
    {
        self::$model::where('id', '>=', 263)->where('id', '<=', 272)->delete();
    }
}

运行测试

cd {APPROOT}/tests/Cases/Headline
# mv phpunit-debug-custom.xml -> phpunit-debug.xml
../../../vendor/bin/phpunit --verbose -c phpunit-debug.xml

参考

PHPUnit 5.0 官方中文手册

相关推荐