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
/**
 * Created by PhpStorm.
 * User: qikailin
 * Date: 2019-01-29
 * Time: 16:12
 */
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;
    }
}

demo

<?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/ArticleCategoryRelationModel.php</file>
                <file>../../../app/Models/Headline/ArticleContentModel.php</file>
                <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 官方中文手冊

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章