0%

一个 test 多个请求

$this->post,然后又 $this->post,我们会发现第二个请求中的请求参数是和第一个请求的参数是完全一样的, 然后在 Controller 里面通过 spl_object_hash 方法发现两个请求的 request 实例是一样的, 应该是第二个请求发起的时候,request 不再实例化,直接使用了上一次请求的 request 实例。

这种情况我们可以用过 request 实例的 replace 方法,替代掉 request 实例的请求参数,这样我们第二个请求就可以按照我们预期地跑了。

1
2
3
4
5
6
7
$this->post('xxx', ['a' => 1]);

app('request')->replace([
'b' => 2
]);

$this->post('yyy');

使用 mock 的时候,mock 一个不存在的方法不会报错

其实这也算是 mock 本身要实现的功能,但是如果我们可能在调用多个对象的方法的时候会混淆,mock 了一个错误的对象的方法,但实际上应该是 mock 另外一个。

如果我们 mock 了之后,对象方法表现还是原来的样子就应该考虑一下是不是 mock 了一个错误的对象。

不能对 private final static 方法进行 mock,需要对 mock 的对象调用 setMethods,说明我们要对哪些方法进行 mock,否则可能会报错。

phpunit.xml 中添加 dingo 相关配置

1
2
3
4
5
6
<env name="API_STANDARDS_TREE" value="x"/>
<env name="API_SUBTYPE" value="prime"/>
<env name="API_VERSION" value="v1"/>
<env name="API_DEFAULT_FORMAT" value="json"/>
<env name="API_STRICT" value="false"/>
<env name="API_PREFIX" value="api"/>

dingo 的路由配置文件包含不能使用 require_once

否则, 可能会出现一种情况是, phpunit 中第一个请求成功了, 但是后面的请求都 404。

这里涉及到的一个知识点是:laravel 或 lumen phpunit 中每一个 test 都会使用独立的 Application 以及 TestCase 实例来运行,我们知道,框架在初始化的时候会加载路由的配置,如果我们的进程只跑一遍,这样其实没什么问题。

但是,phpunit 中的 TestCase 是,一个进程,然后一个 TestCase 里面有多少 test 方法,就会进行多少次框架的初始化操作,这样问题就出现了,后续的请求中,require_once 不能再读取到任何配置信息,因此导致了后续请求全部 404 了。

所以需要使用 require 代替 require_once

知识点:同一个进程中,require_once 只有第一次 require 会执行文件里面的内容,后续 require 不再加载文件。

在测试中添加以下方法调用

  • phpunit.xml 中添加 dingo 相关配置
1
2
3
4
5
6
<env name="API_STANDARDS_TREE" value="x"/>
<env name="API_SUBTYPE" value="prime"/>
<env name="API_VERSION" value="v1"/>
<env name="API_DEFAULT_FORMAT" value="json"/>
<env name="API_STRICT" value="false"/>
<env name="API_PREFIX" value="api"/>
  • dingo 的路由配置文件包含不能使用 require_once。否则, 可能会出现一种情况是, phpunit 中第一个请求成功了, 但是后面的请求都 404

  • 在测试中添加以下方法调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public function setUp()
{
parent::setUp();

// 跳过 api 验证
$user = \Modules\User\Models\User::with(['company'])->find(61339);
$this->actingAsApi($user);
}

/**
* Sets the API user.
*
* @param mixed $user
* @return $this
*/
protected function actingAsApi($user)
{
// mock service middleware
/** @var Mockery\Mock $auth */
$auth = Mockery::mock('Dingo\Api\Http\Middleware\Auth[handle]',
[
Mockery::mock('Dingo\Api\Routing\Router'),
Mockery::mock('Dingo\Api\Auth\Auth'),
]);
$auth->shouldReceive('handle')
->andReturnUsing(function ($request, \Closure $next) {
return $next($request);
});
$this->app->instance('Dingo\Api\Http\Middleware\Auth', $auth);
$auth = Mockery::mock('Dingo\Api\Auth\Auth[user]',
[
app('Dingo\Api\Routing\Router'),
app('Illuminate\Container\Container'),
[],
]);
$auth->shouldReceive('user')
->andReturnUsing(function () use ($user) {
return $user;
});
$this->app->instance('Dingo\Api\Auth\Auth', $auth);

return $this;
}

listen-sql 一个在控制台看到实时 sql 操作的工具

在 Laravel 中打印 sql,以往的做法往往是,通过 DB::listen 监听,然后通过 Log::info 写入到 log 中。

这样写入的 log,我们想查看往往是去 storage 文件夹下找到当天的 log 文件,然后打开。有个不好的地方是,如果在编辑器打开,往往不会实时更新。请求完之后,可能需要切到其他 tab 再切换回来才会更新。同时,太多的 sql 日志会和其他 log 混杂在一起,会显得有些混乱。

除此之外,也可以 tail -f storage/logs/xx.log 来实时查看 log 的输出。这样有个不好的地方是,如果在 config/app.php 定义了 log => 'daily',每天都要输入一个新的文件名。

现在,我们可以只使用一个命令来实现监听应用里的 sql 操作。

控制台实时查看 sql

安装

1.通过 composer 安装 (eleven26/listen-sql)。

1
composer require "eleven26/listen-sql:~1.0.2"

2.注册 Service Provider

  • Laravel: 修改文件 config/app.phpLaravel 5.5+ 不需要

    1
    2
    3
    4
    'providers' => [
    //...
    Eleven26\ListenSql\ListenSqlServiceProvider::class,
    ],

  • Lumen: 修改文件 bootstrap/app.php

    1
    $app->register(Eleven26\ListenSql\ListenSqlServiceProvider::class);

使用

1
php artisan listen-sql:start

到这一步,去页面刷新的时候,就可以在控制台看到 sql 语句了

可以使用下面的命令

1
php artisan migrate --pretend --no-ansi  

当然,你需要有可以 migrate 的东西。

数据库迁移导出到文件(使用命令)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php

namespace App\Console\Commands;

use Illuminate\Contracts\Bus\SelfHandling;

class MigrateToSql implements SelfHandling
{
protected $signature = 'migrate_to_sql';

protected $description = '数据库迁移转 sql';

/**
* Execute the command.
*
* @return void
*/
public function handle()
{
$command = PHP_BINARY . ' ' . base_path('artisan') . ' migrate --pretend --no-ansi';
exec($command, $output);

$sql = '';
if (count($output) > 0) {
foreach ($output as $line) {
$sql .= preg_replace('/.*?:/', '', $line) . ";\n";
}
}

$file = database_path('sqls/' . date('Y-m-d H:i:s') . '.sql');
file_put_contents($file, $sql);
}
}

上面的一些处理是把一些无效的信息去掉,如时间戳,这样最后剩下的就是可以直接执行的 sql 语句了。

envoy 是什么?

envoy 是一个支持 blade 语法的 ssh 远程命令执行的工具。具体来说就是,通过配置 ssh 的账号、密码、key 这些,然后可以使用 envoy 运行一些预定义的命令(比如 git 更新什么的)。

安装
1
composer global require laravel/envoy
怎么使用?

配置(~/.ssh/config):

关于 ssh key 配置官网文档没有说,但是 Google 出来了,需要在 ~/.ssh/config 下面定义你每个 server 对应的 key file,我们假设里面有如下内容:

1
2
3
Host 12.34.56.78
IdentityFile ~/.ssh/id_rsa
User root

如果我们有另外一个服务器,那么可以按如上格式再另写三行,以此类推。

配置有了,接下来就是写具体命令了:

在项目根目录新建一个 Envoy.blade.php 文件,这个官网文档有说,第一行定义 servers,注意,不能换行,否则会报错,也许是个 bug,如:

1
@servers(['web' => '127.0.0.1', 'test' => '12.34.56.78'])

我们假设有一个更新项目的操作,如:

1
2
3
4
5
6
7
@task('update', ['on' => ['web', 'test']])
cd "/path/to/project"
{{PHP_BINARY}} artisan config:cache
{{PHP_BINARY}} artisan route:cache
{{PHP_BINARY}} artisan optimize --force
"/composer/binary" dumpautoload
@endtask

这样我们运行下面命令的时候,就会进行一些操作:

1
envoy run update

我们可以使用一些 blade 的语法,如,在 task 中使用变量,这个变量可以是我们在 @setup 中设置的。

tips:对于另外一些操作,比如需要交互的,如可能 git pull 的时候需要输入账号密码的,可以尝试一下 except(当然,不支持 windows)。