[ Laravel从入门到精通 ] 测试系列 —— 通过测试驱动开发构建待办任务项目(一):后端 API 接口篇

栏目: 编程语言 · PHP · 发布时间: 5年前

内容简介:本来打算写一篇通过 Laravel Dusk 测试前端 Vue 组件的教程,转念一想不如玩把大的,直接基于 Laravel + Vue 构建一个前后端分离的待办任务列表项目,然后在开发过程中通过 HTTP 功能测试用例测试后端 API 接口,通过浏览器测试用例测试前端 Vue 组件与后端的交互,同时引入数据库测试对增删改查进行测试,从而完成一个简单的、相对完整的测试驱动开发项目。我们创建一个新的 Laravel 应用来完成项目。首先通过如下命令快速初始化一个新的 Laravel 应用,将新项目命名为新项目创

本来打算写一篇通过 Laravel Dusk 测试前端 Vue 组件的教程,转念一想不如玩把大的,直接基于 Laravel + Vue 构建一个前后端分离的待办任务列表项目,然后在开发过程中通过 HTTP 功能测试用例测试后端 API 接口,通过浏览器测试用例测试前端 Vue 组件与后端的交互,同时引入数据库测试对增删改查进行测试,从而完成一个简单的、相对完整的测试驱动开发项目。

构建应用

创建新项目

我们创建一个新的 Laravel 应用来完成项目。首先通过如下命令快速初始化一个新的 Laravel 应用,将新项目命名为 todoapp

laravel new todoapp

数据库迁移

新项目创建后,进入 todoapp 目录,修改 .env 中的数据库相关配置以便可以连接到本地开发环境的数据库。然后运行如下 Artisan 命令创建一个新的数据库迁移文件用于创建待办任务表 tasks

php artisan make:migration create_tasks_table

该命令会在 database/migrations 目录下生成一个数据库迁移文件 2019_04_16_054738_create_tasks_table.php ,编写该文件代码如下:

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateTasksTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('tasks', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->text('text');
            $table->tinyInteger('is_completed')->unsigned()->default(0);
            $table->integer('user_id')->unsigned();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('tasks');
    }
}

我们在这个数据表中定义三个功能字段, text 用于存放任务名称, is_completed 用于表示任务是否完成, user_id 用于存放对应的用户 ID 以便和用户关联。运行 Artisan 迁移命令在数据库中创建这张数据表:

php artisan migrate

创建模型类

接下来,通过如下 Artisan 命令创建 tasks 对应的模型类:

php artisan make:model Task

该命令会在 app 目录下生成 Task.php ,编写该模型类代码如下:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Task extends Model
{
    const NOT_COMPLETED = 0;
    const IS_COMPLETED = 1;

    protected $fillable = ['text', 'is_completed', 'user_id'];

    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

我们在模型类中定义了两个常量属性用于标识任务是否完成,通过 $fillable 属性设置了支持批量赋值的属性,最后定义了一个 user 方法用于表示用户与待办任务之间的一对多关联关系。

定义资源控制器

为了实现对待办任务的增删改查操作,我们为其创建一个资源控制器 TaskController

php artisan make:controller TaskController --resource

该命令会在 app/Http/Controllers 目录下生成一个资源控制器 TaskController.php 。在这个控制器中,我们限制只有认证用户才能对任务进行增、删、改操作,未认证游客只能查看任务,由于我们要构建的是前后端分离应用,所以需要通过 auth:api 中间件对未认证用户进行判断,我们暂时先实现其中的 storeupdatedelete 三个资源变更方法:

<?php

namespace App\Http\Controllers;

use App\Task;
use Illuminate\Http\Request;

class TaskController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth:api')->except(['index', 'show']);
    }

    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        //
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        //
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        $request->validate([
            'text' => 'required'
        ]);

        return Task::create([
            'text' => $request->text,
            'user_id' => auth('api')->user()->id,
            'is_completed' => Task::NOT_COMPLETED
        ]);
    }

    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
        //
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function edit($id)
    {
        //
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  Task  $task
     * @return \Illuminate\Http\Response
     */
    public function update(Task $task)
    {
        return tap($task)->update(request()->only(['is_completed', 'text']))->fresh();
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  Task  $task
     * @return \Illuminate\Http\Response
     */
    public function destroy(Task $task)
    {
        $task->delete();

        return response()->json(['message' => 'Task deleted'], 200);
    }
}

在上述代码中,我们用到了 Laravel 框架的控制器中间件、 隐式路由绑定表单字段验证 、辅助函数 tap/auth 以及JSON 响应等功能特性,对这些功能不太熟悉的话,可以点击链接查看相应的文档。

注册路由

定义好控制器后,我们在 API 路由文件 routes/api.php 中定义相应的 API 资源路由指向这个控制器:

Route::resource('task', 'TaskController');

Passport 初始化

由于我们在这个项目中需要用到 API 认证,并且将基于Passport 扩展包实现 API 认证,所以还需要通过 Composer 安装这个扩展包:

composer require laravel/passport

然后运行如下 Artisan 命令初始化 Passport 数据表和认证相关密钥信息:

php artisan migrate
php artisan passport:install

最后,不要忘了添加 Laravel\Passport\HasApiTokens Trait 到 App\User 模型,以便可以在 User 模型上使用 Passport 进行 API 认证:

use Laravel\Passport\HasApiTokens;

class User extends Authenticatable
{
    use Notifiable, HasApiTokens;

    ...

好了,至此,我们就完成了该项目后端功能的初始化工作,下面我们通过编写HTTP 功能测试用例来测试上面定义的三个 API 接口。

编写 HTTP 功能测试用例

编写测试用例 TasksTest

我们还是通过一条 Artisan 命令来生成功能测试用例类:

php artisan make:test TasksTest

该命令会在 tests/Feature 目录下创建一个新的 TasksTest.php 文件,我们在这个文件中定义 HTTP 功能测试代码如下:

<?php

namespace Tests\Feature;

use Tests\TestCase;
use App\Task;
use App\User;
use Laravel\Passport\Passport;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;

class TasksTest extends TestCase
{
    use DatabaseMigrations;

    /**
     * 测试认证用户可以创建任务
     */
    public function testUserCanCreateTask()
    {
        $user = factory(User::class)->create();
        $task = [
            'text' => 'New task text',
            'user_id' => $user->id
        ];

        Passport::actingAs($user, ['*']);
        $response = $this->json('POST', 'api/task', $task);

        $response->assertStatus(201);
        $this->assertDatabaseHas('tasks', $task);
    }

    /**
     * 测试访客不能创建任务
     */
    public function testGuestCantCreateTask()
    {
        $task = [
            'text' => 'new text',
            'user_id' => 1
        ];

        $response = $this->json('POST', 'api/task', $task);

        $response->assertStatus(401);
        $this->assertDatabaseMissing('tasks', $task);
    }

    /**
     * 测试认证用户可以删除任务
     */
    public function testUseCanDeleteTask()
    {
        $user = factory(User::class)->create();
        $task = factory(Task::class)->create([
            'text' => 'task to delete',
            'user_id' => $user->id
        ]);

        Passport::actingAs($user, ['*']);
        $response = $this->json('DELETE', "api/task/$task->id");

        $response->assertStatus(200);
        $this->assertDatabaseMissing('tasks', ['id' => $task->id]);
    }

    /**
     * 测试认证用户可以完成任务
     */
    public function testUserCanCompleteTask()
    {
        $user = factory(User::class)->create();
        $task = factory(Task::class)->create([
            'text' => 'task to complete',
            'user_id' => $user->id
        ]);

        Passport::actingAs($user, ['*']);
        $response = $this->json('PUT', "api/task/$task->id", ['is_completed' => Task::IS_COMPLETED]);

        $response->assertStatus(200);
        $this->assertNotNull($task->fresh()->is_completed);
    }
}

在上述代码中,我们编写了四个测试用例,分别用于测试创建任务、删除任务和更新任务接口,并且在创建任务的时候,为了测试未登录游客不能创建任务,还编写了额外的一个测试用例 testGuestCantCreateTask

涉及到的相关测试技术

在测试用例中,我们不仅会断言 API 接口的响应状态码,还会断言调用接口后数据库中的对应记录是否存在,以确认更新操作确实生效。有关 HTTP 功能测试和数据库测试的更多断言方法可以参考HTTP 测试和数据库测试文档。

在需要用户认证的场景下,我们使用了Passport 提供的方法模拟用户进行 API 认证。

另外,你可能注意到,我们还在测试类中使用了 DatabaseMigrations Trait,它的作用是在运行测试用例之前运行 migrate:refresh 命令回滚所有迁移再运行所有迁移,也就是重新构建数据库,然后在应用销毁(测试结束)时运行 migrate:rollback 命令回滚所有已执行的迁移:

<?php

namespace Illuminate\Foundation\Testing;

use Illuminate\Contracts\Console\Kernel;

trait DatabaseMigrations
{
    /**
     * Define hooks to migrate the database before and after each test.
     *
     * @return void
     */
    public function runDatabaseMigrations()
    {
        $this->artisan('migrate:fresh');

        $this->app[Kernel::class]->setArtisan(null);

        $this->beforeApplicationDestroyed(function () {
            $this->artisan('migrate:rollback');

            RefreshDatabaseState::$migrated = false;
        });
    }
}

编写模型工厂

在这段测试代码中,我们还使用了模型工厂模拟创建数据库记录,由于 User 模型对应的模型工厂 Laravel 框架已经开箱提供,所以我们只需要创建 Task 模型对应的模型工厂即可,我们使用如下 Artisan 命令创建模型工厂:

php artisan make:factory TaskFactory

该命令会在 database/factories 目录下创建 TaskFactory.php ,我们就在这个文件中编写模型工厂:

<?php

use Faker\Generator as Faker;

$factory->define(\App\Task::class, function (Faker $faker) {
    return [
        'text' => $faker->text,
        'is_completed' => \App\Task::NOT_COMPLETED
    ];
});

至此,我们的 HTTP 测试用例就编写好了,接下来我们就可以运行这个测试用例来检验代码是否有问题了。

运行 HTTP 测试用例

我们在项目根目下运行如下命令执行测试用例,绿色代表通过:

[ Laravel从入门到精通 ] 测试系列 —— 通过测试驱动开发构建待办任务项目(一):后端 API 接口篇

这就说明我们的后端 API 接口都是 OK 的,如果测试不通过,则需要排查问题,修改代码,直到测试都通过。

下一篇我们将基于 Vue 组件构建前端页面,通过 JavaScript 与后端这些 API 接口交互实现完整的增删改查功能,并通过 Laravel Dusk 编写浏览器测试用例验收整个项目是否合格。


以上所述就是小编给大家介绍的《[ Laravel从入门到精通 ] 测试系列 —— 通过测试驱动开发构建待办任务项目(一):后端 API 接口篇》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Algorithms Illuminated (Part 2)

Algorithms Illuminated (Part 2)

Tim Roughgarden / Soundlikeyourself Publishing, LLC / 2018-8-5 / USD 17.99

Algorithms are the heart and soul of computer science. Their applications range from network routing and computational genomics to public-key cryptography and machine learning. Studying algorithms can......一起来看看 《Algorithms Illuminated (Part 2)》 这本书的介绍吧!

SHA 加密
SHA 加密

SHA 加密工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具