本系列教程所有的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"?><phpunitbootstrap="../../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为引用对象,不是copyif ($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 官方中文手册