测试驱动开发一直被提起,但是该如何实施时候却又总是一头雾水,这里会通过laravel 5.6实现一个简单注册功能来讲下如何实施测试驱动开发
理清需求与功能分析
这是测试驱动开发的第一步,首先需要对需求有一定的理解,这关系到后面测试用例的编写
我们要实现的是一个注册功能,需要用户填入username和password字段,同时还需要验证这两个字段的合法性
准备工作
配置数据库连接
首先打开 config/database.php 中加入
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 加入
它的作用是用来添加或替换掉 DB_CONNECTION 环境变量,当跑测试用例的时候使用 mysql_test 这个数据库连接,环境变量分为下面两个
- 系统环境变脸
- 本地环境变量,即是项目根目录的.env中的配置
创建数据表
这里只是实现个注册功能,所以只需要一个表
php artisan make:migrate create_user_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 artisan make:factory UserFactory
打开UserFactory编辑假数据生成的规则
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
编辑测试用例
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
具体如何做?其实很简单
- 在你想要debug的地方打好断点
- 开启debug
- 跑测试用例
当然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