Menu

测试

测试

简介

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 请求,可以在请求时传入一个数组作为请求参数。当然,你也可以在路由及控制器中通过 请求实例来获取传过来的参数:

$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 特性,它可以自动为您处理。简单的在您的测试类中运用这个特性如下:

<?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 特性,可以为您自动的执行这些:

<?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 models 定义一组默认属性。首先,在您的应用中 看看 database/factories/ModelFactory.php 文件。这个文件包含一个工厂定义:

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

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

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

多种工厂类型

有时您可能希望同一个 Eloquent 模型有多种工厂。例如,可能您会希望除了普通的用户之外还有管理员用户的工厂。您可能会使用 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 方法创建模型实例,还可以使用 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...
    }
}

模拟任务

有时你可能希望当请求发送至应用程序时,简单的对控制器所派送的任务进行测试。这么做能够让你隔离测试路由或控制器,设置除了任务以外的逻辑。当然,在此之后你也可以在一个单独的测试案例中来测试该任务。

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 门面,它会返回一个 Mockery模拟的实例。因为门面实际上已经被 Lumen 的 服务容器, 解析和管理,它们比一般的静态类更具有可测性。例如,让我们模拟调用 Cache 门面:

<?php

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

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

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

本文章首发在 Laravel China 社区
上一篇 下一篇
讨论数量: 0
发起讨论


暂无话题~