测试

未匹配的标注

测试

简介

Lumen 在创建时就已考虑到测试的部分。事实上,Lumen 默认就支持用 PHPUnit 来做测试,并为你的应用程序创建好了 phpunit.xml 文件。 框架还提供了一些便利的辅助函数,让你可以更直观的测试应用程序的 JSON 响应。

tests 目录下已经提供了一个 ExampleTest.php 示例文件。安装新的 Lumen 应用程序之后,只需在命令行上运行 phpunit 就可以进行测试。

测试环境

在运行测试时,Lumen 自动将缓存驱动配置为 array ,意味着在测试的时候不会保存任何的缓存数据。

你可以随意创建其他必要的测试配置环境。 testing 的环境变量可以在 phpunit.xml 文件中进行修改。

定义和运行测试

要创建一个测试用例,直接将新的测试文件创建到 tests 文件夹下即可。测试文件必须继承 TestCase。接着就可以像平常使用 PHPUnit 一样来定义测试方法。要运行测试只需要在命令行上运行 phpunit 命令即可:

<?php

class FooTest extends TestCase
{
    public function testSomethingIsTrue()
    {
        $this->assertTrue(true);
    }
}

注意: 如果要在你的类自定义 setUp 方法,请确保调用了 parent::setUp

应用测试

Lumen 提供了一个非常好用的 API,用来向你的应用发起 HTTP 请求,并查看输出结果。

测试 JSON API 接口

Lumen 同样提供了一些辅助函数用于测试 JSON API 及其响应。例如,getpostputpatchdelete 方法可以被用于发起各种方式的 HTTP 请求。同样,你也可以很轻松地给这些方法传递数据或者头部信息。首先,我们要创建一个测试,向 /user 发起 POST 请求,并且断言以 JSON 格式返回一个指定的数组:

<?php

class ExampleTest extends TestCase
{
    /**
     * 一个简单的测试例子
     *
     * @return void
     */
    public function testBasicExample()
    {
        $this->json('POST', '/user', ['name' => 'Sally'])
             ->seeJson([
                 'created' => true,
             ]);
    }
}

seeJson 方法将给定的数组转换成 JSON,并验证这个 JSON 片段发生在应用返回的整个 JSON 响应的 任意位置 。所以,即使在 JSON 响应中存在其他属性,只要指定的片段存在,这个测试仍然会成功。

验证完全匹配的 JSON

如果你想验证传入的数组是否与应用程序返回的 JSON 完全 匹配, 你可以用 seeJsonEquals 方法:

<?php

class ExampleTest extends TestCase
{
    /**
     * 一个简单的测试例子
     *
     * @return void
     */
    public function testBasicExample()
    {
        $this->post('/user', ['name' => 'Sally'])
             ->seeJsonEquals([
                 'created' => true,
             ]);
    }
}

认证

actingAs 辅助函数提供了简单的方式来让指定的用户认证为当前的用户:

<?php

class ExampleTest extends TestCase
{
    public function testApplication()
    {
        $user = factory('App\User')->create();

        $this->actingAs($user)
             ->get('/user');
    }
}

自定义 HTTP 请求

如果你想要创建一个自定义的 HTTP 请求到应用程序上,并获取完整的 Illuminate\Http\Response 对象,可以使用 call 方法:

public function testApplication()
{
    $response = $this->call('GET', '/');

    $this->assertEquals(200, $response->status());
}

如果你想构造 POSTPUT, 或者 PATCH 请求,可以在请求时传入一个数组作为请求参数。当然,你也可以在路由及控制器中通过 Request 实例 来获取传过来的参数:

   $response = $this->call('POST', '/user', ['name' => 'Taylor']);

使用数据库

Lumen 还提供了各种有用的工具,使测试数据库驱动的应用程序变得更容易。首先,你可以使用 seeInDatabase 辅助函数来断言数据库中存在与给定条件集匹配的数据。例如,我们想要验证 users 表中有一条 email 的值为 sally@example.com 的记录,我们可以按如下操作:

public function testDatabase()
{
    // Make call to application...

    $this->seeInDatabase('users', ['email' => 'sally@foo.com']);
}

当然, seeInDatabase 方法和其他类似的辅助函数就是为了方便使用。你也可以在测试中自由使用 PHPUnit 的内置断言方法。

每次测试之后重置数据库

在每次测试后重置数据库是非常有必要的,这样之前的测试数据不会影响后面的测试。

使用迁移

有一个选择是每次测试之后回滚数据库,并且在下一次测试之前将其迁移。Lumen 提供了一个简单的 DatabaseMigrations trait,它可以自动为您处理。简单的在您的测试类中运用这个 trait 如下:

<?php

use Laravel\Lumen\Testing\DatabaseMigrations;
use Laravel\Lumen\Testing\DatabaseTransactions;

class ExampleTest extends TestCase
{
    use DatabaseMigrations;

    /**
     * A basic functional test example.
     *
     * @return void
     */
    public function testBasicExample()
    {
        $this->get('/foo');
    }
}

使用事务

另一个选择是将每一个测试用例包装在数据库事务中。同样,Lumen 提供了一个便利的 DatabaseTransactions trait,可以为您自动的执行这些:

<?php

use Laravel\Lumen\Testing\DatabaseMigrations;
use Laravel\Lumen\Testing\DatabaseTransactions;

class ExampleTest extends TestCase
{
    use DatabaseTransactions;

    /**
     * A basic functional test example.
     *
     * @return void
     */
    public function testBasicExample()
    {
        $this->get('/foo');
    }
}

模型工厂

在测试时,通常需要在执行测试之前将少量的数据记录插入到数据库。Lumen 允许您使用「factories」为每个 Eloquent 模型定义一组默认属性,而不是在创建这些数据时手动指定每一列的值。首先,请查看应用中的 database/factories/ModelFactory.php 文件。开箱即用,此文件包含一个工厂定义:

$factory->define('App\User', function ($faker) {
    return [
        'name' => $faker->name,
        'email' => $faker->email,
    ];
});

在作为工厂定义的闭包中,您可以返回模型上所有属性的默认测试值。 闭包将接收 Faker PHP 库的一个实例,它允许您方便地生成各种随机数据以用于测试。

当然,您可以自由地将自己的附加工厂添加到 ModelFactory.php 文件中 。

多种工厂类型

有时您可能希望同一个 Eloquent 模型有多种工厂。例如,也许您希望除了普通的用户之外还为「Administrator」用户提供工厂。您可以使用 defineAs 方法定义这些工厂:

$factory->defineAs('App\User', 'admin', function ($faker) {
    return [
        'name' => $faker->name,
        'email' => $faker->email,
        'admin' => true,
    ];
});

您可以使用 raw 方法检索其基本属性,而不是复制基本用户工厂中的所有属性。获取属性后,只需用您所需的任何其他值补充它们即可:

$factory->defineAs('App\User', 'admin', function ($faker) use ($factory) {
    $user = $factory->raw('App\User');

    return array_merge($user, ['admin' => true]);
});

在测试中使用工厂

在工厂定义后,就可以在测试或是数据库的填充文件中,通过全局的 factory 函数来生成模型实例。接着让我们来看几个创建模型的例子。首先我们会使用 make 方法创建模型,但不将它们保存至数据库:

public function testDatabase()
{
    $user = factory('App\User')->make();

    // 在测试中使用模型...
}

如果你想重写模型中的某些默认值,则可以传递一个包含数值的数组至 make 方法。只有指定的数值会被替换,其余的值仍设置为工厂指定的默认值:

$user = factory('App\User')->make([
    'name' => 'Abigail',
   ]);

你还可以创建许多模型的集合或创建给定类型的模型:

// Create three App\User instances...
$users = factory('App\User', 3)->make();

// Create an App\User "admin" instance...
$user = factory('App\User', 'admin')->make();

// Create three App\User "admin" instances...
$users = factory('App\User', 'admin', 3)->make();

持久化工厂模型

create 方法不仅创建模型实例,还使用 Eloquent 的 save 方法将它们保存到数据库:

public function testDatabase()
{
    $user = factory('App\User')->create();

    // Use model in tests...
}

同样,你也可以通过将数组传递给 create 方法来覆盖模型上的属性:

$user = factory('App\User')->create([
    'name' => 'Abigail',
   ]);

添加关联至模型

你甚至可以将多个模型持久化到数据库中。在本例中,我们甚至将一个关联附加到创建的模型上。当使用 create 方法创建多个模型时,将返回一个 Eloquent 集合实例,让你能使用集合提供的便捷函数,例如 each 函数:

$users = factory('App\User', 3)
           ->create()
           ->each(function($u) {
                $u->posts()->save(factory('App\Post')->make());
            });

模拟

模拟事件

如果你大量地使用 Lumen 的事件系统,你可能会希望在测试时静默或者需要模拟某些事件。例如,如果你在测试用户注册,你可能不希望所有的 UserRegistered 事件被触发,因为它们会发送「欢迎」邮件,等等。

Lumen 提供了简便的 expectsEvents 方法,以验证是否触发了预期的事件,但是会阻止该事件的任何处理程序的运行:

<?php

class ExampleTest extends TestCase
{
    public function testUserRegistration()
    {
        $this->expectsEvents('App\Events\UserRegistered');

        // 测试用户注册功能...
    }
}

如果你想阻止所有的事件处理程序运行,你可以使用 withoutEvents 方法:

<?php

class ExampleTest extends TestCase
{
    public function testUserRegistration()
    {
        $this->withoutEvents();

        // Test user registration code...
    }
}

模拟任务

有时,你可能希望在向应用发送请求时,简单的测试控制器是否调度了特定的任务(Jobs)。这允许你隔离测试路由或控制器——与你的任务(Job)逻辑分开。当然,此后你可以在一个单独的测试类中测试该任务(Job)。

Lumen 提供了一个简便的 expectsJobs 方法,以验证预期的任务有没有被派送,但任务本身不会被运行:

<?php

class ExampleTest extends TestCase
{
    public function testPurchasePodcast()
    {
        $this->expectsJobs('App\Jobs\PurchasePodcast');

        // 测试购买播客代码……
    }
}

注意: 该方法只检测通过全局辅助函数 dispatch 或者由路由或控制器中的 $this->dispatch 方法派送的任务。它并不会检测被直接发送到 Queue::push 的任务。

模拟 Facades

在测试时,经常需要模拟对 Lumen facade的调用。例如,考虑如下控制器的操作:

<?php

namespace App\Http\Controllers;

use Cache;

class UserController extends Controller
{
    /**
     * 展示应用程序所有用户的列表。
     *
     * @return Response
     */
    public function index()
    {
        $value = Cache::get('key');

        //
    }
}

我们可以使用 shouldReceive 方法来模拟调用 Cache facade,它会返回一个 Mockery mock 的实例。因为门面实际上由 Lumen 的 服务容器 解析和管理,它们比典型的静态类更具有可测性。例如,让我们模拟调用 Cache 门面:

<?php

class FooTest extends TestCase
{
    public function testGetIndex()
    {
        Cache::shouldReceive('get')
                    ->once()
                    ->with('key')
                    ->andReturn('value');

        $this->get('/users');
    }
}

注意: 你不应该模拟 Request 门面。应该在测试时使用如 callpost 这样的 HTTP 辅助函数来传递你想要的数据。

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
上一篇 下一篇
Summer
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
贡献者:2
讨论数量: 0
发起讨论 只看当前版本


暂无话题~