概述
- 折腾了一个上午搞这个单元测试,的亏是通了,不然要面壁去了。
thinkphp6官方并没有支持phpunit的集成使用,不知道为什么,虽然不妨碍我们直接使用phpuint,单局限性很大,我们只能对通用类和不涉及框架层面进行测试,如接口验证之类。这样的单元测试意义并不大,不能注入到核心里面,还不如直接看接口返回得了,造啥测试,既然用了必然是想要代码质量更高的。
打通任督二脉
想要运行并测试框架内部的逻辑,那么怎么引入就成了最大的问题,官方没有说明,这里是基于折腾过的文章进行验证通过的。
主要原理就是,通过构建请求信息Request对象,将其注入到初始化的框架APP里面,要手动初始化框架,如果框架具备的上文,初始化后,通过app对象请求指定的方法和控制器,如此就有了下文。就可以通过下文返回得结果进行断言等操作。
研究thinkphp6的phpunit整合血泪史
https://blog.yongit.com/note/782453.html
首先安装 phpuint
composer require phpunit/phpunit --dev
1.测试控制器方法产出结果
<?php
declare (strict_types=1);
namespace tests;
final class DemoTest extends \PHPUnit\Framework\TestCase
{
/**
* 这里是测试控制的某个方法产出的结果
*
* @test
*/
public function testControllerDemo(): void
{
$request = new \app\Request();
$request->setMethod('POST');
// 路由地址
$request->setUrl('/index/home');
//$request->withGet(['xid' => 99,]);
//$request->withPost(['aaa' => 111, 'bbb' => 222,]);
$request->withHeader(['HTTP_ACCEPT' => 'application/json',]);
// 初始化框架,并将请求信息类注入到框架里面
$app = (new \think\App());
$app->http->run($request);
// 调用框架方法,这里可以按命名空间直接调用
$userController = new UserController($app);
// 请求接口的控制器方法
$response = $userController->index();
// 获取接口返回得body内容
$content = $response->getContent();
// 可以进行你的断言操作了
$this->assertEquals("success", $content);
}
/**
* @test
*/
public function testDemo1(): void
{
//断言为真
$this->assertTrue(true);
}
}
在项目根目录下运行单元测试,指定文件:
php ./vendor/bin/phpunit tests/DemoTest.php
如果运行过程中不通,可能是你的php拓展缺乏,你的接口底层可能需要进行异常捕捉查看缺少了啥。
起飞,飞高点
composer加入tests目录命名空间
执行composer install 更新目录命名空间关联
"autoload": {
"psr-4": {
"app\\": "app",
"tests\\": "tests"
},
},
按封装一个访问类
<?php
namespace tests\Library;
/**
* Class TestExtend
* @package tests\Library
*/
trait TestExtendTrait
{
/**
* 构造一个请求应用,测试控制器
*
* @param array $data 入参数组
* @param string $method 请求方法
* @return \think\App
*/
function visitorJson($controller, $action, $data, $uid = null, $method = 'POST')
{
if (is_numeric($uid)) {
}
$app = new \think\App();
#$app->request->setMethod($method);
//$app->request->setController('Xlogin');
//$app->request->setUrl('/index/debug/testing26');
//$app->request->setUrl('/index/oauth/login');
//$app->request->setBaseUrl('/index/oauth/login');
$app->request->setUrl("/{$controller}/{$action}}");
//$app->request->withGet(['xid' => 99,'xx' => 'get-xx','yy' => 'get-yy',]);
//$app->request->withGet($data);
//$app->request->withPost(['aaa' => 111, 'bbb' => 222,'xx' => 'post-xx','yy' => 'post-yy',]);
#$app->request->withPost($data);
$request = new \app\Request();
$request->withPost($data);
$request->withRoute($data); //tp6的bug啊,一行代码曲线救国
$response = $app->http->run($request);
//重置请求对象
$app->request->setMethod($method);//非必须
$app->request->setAction($action);
//$app->request->withGet($data);
//$app->request->withPost($data);
//$response->send();
$app->http->end($response);
$controller = "\\app\\controller\\" . $controller;
$test = new $controller($app);
$result = $test->$action();
$content = $result->getContent();
return json_decode($content, true);
}
}
自己封装一个trait类,用户use 方便调用
Demo:
$content = $this->visitorJson($controller='User', $action='getGroupInfo', $params=['id' => 1111]);
$this->assertNotEmpty($content['data'], 'user can not empty.');
这里就有疑问了,model层,service层怎么用?
由于这些内部层是无法直接被外部调用的,而controller之所以可以是因为注入了请求和响应,具有上下文关系,所以我们也要去框架内部实现一个上下文,比如底层controller提供一个调用model的方法或提供一个调用service的工厂方法,如此,可以获取app的时候,直接去调工厂方法来实现某个service,这里只提供一个思路,就不继续了,后续有空再补充。