通过一个简单的注册功能来理解测试驱动开发

测试驱动开发一直被提起,但是该如何实施时候却又总是一头雾水,这里会通过laravel 5.6实现一个简单注册功能来讲下如何实施测试驱动开发

理清需求与功能分析

这是测试驱动开发的第一步,首先需要对需求有一定的理解,这关系到后面测试用例的编写

我们要实现的是一个注册功能,需要用户填入username和password字段,同时还需要验证这两个字段的合法性

准备工作

配置数据库连接

首先打开 config/database.php 中加入

<?php
return [
    'default' => env('DB_CONNECTION', 'mysql'),
    'connections' => [
        // 测试开发用的数据库配置
        'mysql_test' => [
            'driver' => 'mysql',
            'host' => env('DB_HOST', '127.0.0.1'),
            'port' => env('DB_PORT', '3306'),
            'database' => env('DB_DATABASE', 'forge'),
            'username' => env('DB_USERNAME', 'forge'),
            'password' => env('DB_PASSWORD', ''),
        ],
    ],
];

然后打开 phpunit.xml 加入

<env name="DB_CONNECTION" value="mysql_test"/>

它的作用是用来添加或替换掉 DB_CONNECTION 环境变量,当跑测试用例的时候使用 mysql_test 这个数据库连接,环境变量分为下面两个

  • 系统环境变脸
  • 本地环境变量,即是项目根目录的.env中的配置

创建数据表

这里只是实现个注册功能,所以只需要一个表

php artisan make:migrate create_user_table 

实现

<?php

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

class CreateUserTable extends Migration
{
    /**
     * Run the migrations.
     */
    public function up()
    {
        if (!Schema::hasTable('user')) {
            Schema::create('user', function (Blueprint $table) {
                $table->engine = 'InnoDB';

                $table->increments('id');
                $table->string('username', 20)->default('')->coment('用户名');
                $table->string('password', 32)->default('')->comment('密码');
                $table->timestamps();

                $table->index('username');
            });
        }
    }

    /**
     * Reverse the migrations.
     */
    public function down()
    {
        Schema::dropIfExists('user');
    }
}

生成数据表

php artisan migrate

model文件初始化

php artisan make:model User

指明model用的数据表

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    protected $table = 'user';
}

创建模型工厂

模型工厂的作用在于帮助我们测试的时候快速生成假数据

php artisan make:factory UserFactory

打开UserFactory编辑假数据生成的规则

<?php

use Faker\Generator as Faker;

$factory->define(App\User::class, function (Faker $faker) {
    return [
        'username' => $faker->unique()->userName,
        'password' => md5('qwerty123456'),
    ];
});

创建控制器与配置路由

php artisan make:controller UserController

配置路由

Route::post('/register', 'UserController@register')->name('register');

编写测试用例

终于到编写测试用例的时候了,首先创建我们的测试文件

// 创建Feature测试
php artisan make:test UserTest
// 创建Unti测试
php artisan make:test UserTest --unit

编辑测试用例

<?php

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
use App\User;

class UserTest extends TestCase
{
    use RefreshDatabase;
    
    private $prefix = '/api/v1';

    // username为空
    public function test_register_username_is_empty()
    {
        $this->post("{$this->prefix}/register", [
            'username' => '',
            'password' => '111111',
        ])
        ->assertStatus(422)
        ->assertSee('username is not empty');
    }

    // username长度大于20
    public function test_register_username_length_gt_20()
    {
        $this->post("{$this->prefix}/register", [
            'username' => 'aaaaaaaaaaaaaaaaaaaaaaaaaaaa',
            'password' => '111111',
        ])
        ->assertStatus(422)
        ->assertSee('username\'s length can\'t great than 20');
    }

    // username不合法
    public function test_register_username_is_invalid_payload_1()
    {
        $this->post("{$this->prefix}/register", [
            'username' => '132asdf',
            'password' => '111111',
        ])
        ->assertStatus(422)
        ->assertSee('username is invalid');
    }

    // username不合法
    public function test_register_username_is_invalid_payload_2()
    {
        $this->post("{$this->prefix}/register", [
            'username' => 'asdfas%sdfsaf',
            'password' => '111111',
        ])
        ->assertStatus(422)
        ->assertSee('username is invalid');
    }

    // username已存在
    public function test_register_username_exists()
    {
        // 往数据库创建一个用户
        $user = factory(User::class)->create();

        $this->post("{$this->prefix}/register", [
            'username' => $user->username,
            'password' => '111111',
        ])
        ->assertStatus(422)
        ->assertSee('username is exists');
    }

    // password为空
    public function test_register_password_is_empty()
    {
        $this->post("{$this->prefix}/register", [
            'username' => 'helbing',
            'password' => '',
        ])
        ->assertStatus(422)
        ->assertSee('password is not empty');
    }

    // password长度大于20
    public function test_register_password_length_gt_20()
    {
        $this->post("{$this->prefix}/register", [
            'username' => 'helbing',
            'password' => 'aaaaaaaaaaaaaaaaaaaaaaaaaaaa',
        ])
        ->assertStatus(422)
        ->assertSee('password\'s length can\'t great than 20');
    }

    // 注册成功
    public function test_register_success()
    {
        $this->post("{$this->prefix}/register", [
            'username' => 'helbing',
            'password' => 'helbing',
        ])->assertStatus(201);
    }
}

其实你会发现上面的每一条测试用例都是我们在开发中必须进行自测的项

实现功能

编写完测试用例就可以开始实现功能了。既然是要实施测试驱动开发,那么我们就要利用上我们已经写好的测试用例。在项目根目录执行命令

vendor/bin/phpunit --filter UserTest::test_register_username_is_empty

我们通过命令跑了下UserTest测试文件里的test_register_username_is_empty用例,很显然肯定是通过不了,这就需要我们实现接口的功能来让测试用例能通过了

看到这里大家应该明白什么是测试驱动开发了吧,在开发时编写一个功能的测试用例,然后一条一条的实现测试用例,如此往复下去,直到实现所有功能

在测试驱动开发中该如何进行debug呢?在测试驱动开发中,还用echo和var_dump进行debug基本是行不通的,这里建议通过xdebug来进行debug

具体如何做?其实很简单

  1. 在你想要debug的地方打好断点
  2. 开启debug
  3. 跑测试用例

《通过一个简单的注册功能来理解测试驱动开发》

当然echo和var_dump还是很好用的,在一些不好使用xdebug的地方也可以通过echo和var_dump来debug,这些都是工具,就看你怎么用它们了

其他

我们通过实现一个简单注册功能来讲解什么是测试驱动开发,但是在实际的实施测试驱动开发还是会有很多坑要踩的

模拟登录态

假设我们开发的接口使用jwt来做验证,那么我们的测试用例可以这么写

public function test_create_article()
{
    // 重置环境,保证每次跑实例的时候环境都是新的
    $this->refreshApplication();
    $this->refreshDatabase();
    
    // 生成一个用户
    $token = $this->createUserAndLogin();
    
    // 使用数据工厂模生成假数据
    $data = factory(Article::class)->make();
    
    $this->post("{$this->prefix}/article", $data, ['HTTP_Authorization' => "Bearer {$token}"])
        ->assertStatus(201);
}

private function createUserAndLogin()
{
    $user = factory(User::class)->create();

    if ($token = JWTAuth::fromUser($user)) {
        return $token;
    }

    return '';
}

模拟队列,上传文件等

这个可以参考官方文档 Mocking

    原文作者:ADEMO
    原文地址: https://segmentfault.com/a/1190000014798366
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞