如何利用数据填充做好测试
Laravel 从 5.1 开始就在数据库中加入了 数据填充 (Seeding),让测试变得更加容易、快捷。
在测试开始之前你要插入 10 个用户(每个用户有一个帖子)或 1000 个用户(每个用户有一个或者多个帖子)这样的数据。
在本教程中,会创建一个测试用例来测试用户模型和用数据填充的方式创建 10 个用户,这 10 个用户中,每个用户都会随机关注另一个用户。
首先,我们需要创建数据库表。
Migration 迁移
创建一个表来存储用户之间的关系,即谁关注了谁。现在修改下 User 的迁移文件。
# database/migrations/2014_10_12_000000_create_users_table.php
class CreateUsersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('email')->unique();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
// following table is storing the relationship between users
// user_id is following follow_user_id
Schema::create('following', function (Blueprint $table) {
$table->integer('user_id')->unsigned()->index();
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->integer('follow_user_id')->unsigned()->index();
$table->foreign('follow_user_id')->references('id')->on('users')->onDelete('cascade');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('following');
Schema::dropIfExists('users');
}
}
接下来,运行迁移。
php artisan migrate
Migration table created successfully.
如果你在用 Laravel 5.4,可能遇到下面的问题:
[Illuminate\Database\QueryException]
SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes (SQL: alter table `users` add unique `users_email_unique`(`email`))
[PDOException]
SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes
别怕,这是 Laravel 5.4 的常见错误,解决办法可以参考这篇 文章。当你照着文章说的方法做完之后,先删除前面创建的表,然后再次运行迁移。
php artisan migrate
Migration table created successfully.
Migrated: 2014_10_12_000000_create_users_table
Migrated: 2014_10_12_100000_create_password_resets_table
数据库准备好了之后,我们就来准备用户模型吧!
用户模型
在这个用例里面,用户模型是测试对象,它有几种方法用来创建和判断用户之间的关系。
# app/User.php
class User extends Authenticatable
{
use Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name', 'email', 'password',
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password', 'remember_token',
];
public function follows(User $user)
{
$this->following()->attach($user->id);
}
public function unfollows(User $user)
{
$this->following()->detach($user->id);
}
public function following()
{
return $this->belongsToMany('App\User', 'following', 'user_id', 'follow_user_id')->withTimestamps();
}
public function isFollowing(User $user)
{
return !is_null($this->following()->where('follow_user_id', $user->id)->first());
}
}
用户模型这样就准备妥当了。 接下来,我们来将数据插入数据库。
数据填充
Laravel 让数据填充变得很简单。 默认情况下,Seeding 类包含了一个 run
方法。 你可以利用查询构建器或 Eloquent 模型工厂来插入数据。
先运行 Artisan 命令来生成填充数据的文件。
php artisan make:seeder UsersTableSeeder
然后,使用 模型工厂 在 run 方法中生成十个用户。
# database/seeds/UsersTableSeeder.php
use App\User;
use Illuminate\Database\Seeder;
class UsersTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$users = factory(User::class, 10)->create();
}
}
再运行 Artisan 命令填充数据。
php artisan db:seed --class=UsersTableSeeder
当然你也可以用已经存在的 DatabaseSeeder 的 run
方法中调用 UsersTableSeeder。(这里是第二种填充数据的方式,可以让你将多个模型填充文件一次过执行完。不懂?看 文档 去)
# database/seeds/DatabaseSeeder.php
class DatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$this->call(UsersTableSeeder::class);
}
}
采用这样的方式,同样达到填充数据的效果,只是你可以不用在命令里面写明要填充的模型。
php artisan db:seed
都准备好了,就可以开始写测试用例了。
测试用例
在本教程中,写这个测试用例的目的是测试用户模型中的关注和取消关注的方法。
现在生成一个新的测试文件来写我们的用力。
php artisan make:test UserTest
先决条件测试
主要测试开始前的基本操作:首先,我们先创建一个测试,用来确保数据库中有十个用户。
# tests/Feature/UserTest.php
use App\User;
class UserTest extends TestCase
{
public function test_have_10_users()
{
$this->assertEquals(10, User::count());
}
}
运行 PHPUnit
phpunit
如果报错的话,就试试看运行 vendor/bin/phpunit
vendor/bin/phpunit
PHPUnit 5.7.17 by Sebastian Bergmann and contributors.
... 3 / 3 (100%)
Time: 2.72 seconds, Memory: 12.00MB
OK (3 tests, 3 assertions)
为什么 phpunit
不起作用? 可能只是因为你的电脑里面没装 phpunit。 不过不碍事,Laravel 已经通过 Composer 预先安装了 PHPUnit。 你可以直接用。 对于本教程的其余部分,都是使用 vendor/bin/phpunit
命令来执行测试。
测试
上一步测试通过之后,就证明你填充的数据和测试用的工具一切正常。 接下来才是本文的重头戏!
# tests/Feature/UserTest.php
public function test_follows()
{
$userA = User::find(2);
$userB = User::find(3);
$userA->follows($userB);
$this->assertEquals(2, $userA->following()->count());
}
public function test_unfollows()
{
$userA = User::find(3);
$userB = User::find(2);
$userA->unfollows($userB);
$this->assertEquals(0, $userA->following()->count());
}
public function test_A_follows_B_and_C()
{
$userA = User::find(1);
$ids = collect([2, 3, 4, 5, 6, 7, 8, 9, 10]);
$random_ids = $ids->random(2);
$userB = User::find($random_ids->pop());
$userC = User::find($random_ids->pop());
$userA->follows($userB);
$userA->follows($userC);
$this->assertEquals(2, $userA->following()->count());
}
再次运行 PHPUnit
vendor\bin\phpunit
PHPUnit 5.7.17 by Sebastian Bergmann and contributors.
...... 6 / 6 (100%)
Time: 1.23 seconds, Memory: 10.00MB
OK (6 tests, 6 assertions)
以上现实所有测试通过! 耶!
好,现在再来运行一次!(先不问为什么好吧?)
vendor\bin\phpunit
PHPUnit 5.7.17 by Sebastian Bergmann and contributors.
..F.F. 6 / 6 (100%)
Time: 1.25 seconds, Memory: 10.00MB
There were 2 failures:
1) Tests\Feature\UserTest::test_follows
Failed asserting that 3 matches expected 2.
C:\xampp\htdocs\TestWithSeed\tests\Feature\UserTest.php:26
2) Tests\Feature\UserTest::test_A_follows_B_and_C
Failed asserting that 4 matches expected 2.
C:\xampp\htdocs\TestWithSeed\tests\Feature\UserTest.php:52
FAILURES!
Tests: 6, Assertions: 6, Failures: 2.
哎呀!糟糕! 两个测试都失败! 这是怎么回事呢?
哈哈!其实,当你第一次运行测试时,测试过程中会更改数据库中的数据。 因此,当你再次运行测试时,更改过的数据可能会出现影响测试结果的情况。但这并不意味着应用程序有错误。(知道这件事情,以后要是遇见测试运行报错的时候,就可以不用惊慌)
测试用例也对这种情况提供了处理方法:每次测试运行之前重置数据库!
重置数据库
建议一,在 PHPUnit 之前运行 php artisan migrate:refresh --seed
。
php artisan migrate:refresh --seed
vendor\bin\phpunit
另一个更好的建议是使用 Traits。
Laravel 提供两种重置数据库的方法:DatabaseMigrations 和 DatabaseTransactions。
我们先来试试 DatabaseMigrations:
# tests/Feature/UserTest.php
class UserTest extends TestCase
{
use DatabaseMigrations;
...
}
运行 phpunit
vendor\bin\phpunit
PHPUnit 5.7.17 by Sebastian Bergmann and contributors.
..EEE. 6 / 6 (100%)
Time: 5.53 seconds, Memory: 12.00MB
There were 3 errors:
1) Tests\Feature\UserTest::test_follows
Error: Call to a member function follows() on null
C:\xampp\htdocs\seeding-data-in-the-testing\tests\Feature\UserTest.php:25
2) Tests\Feature\UserTest::test_unfollows
Error: Call to a member function unfollows() on null
C:\xampp\htdocs\seeding-data-in-the-testing\tests\Feature\UserTest.php:35
3) Tests\Feature\UserTest::test_A_follows_B_and_C
Error: Call to a member function follows() on null
C:\xampp\htdocs\seeding-data-in-the-testing\tests\Feature\UserTest.php:50
ERRORS!
Tests: 6, Assertions: 3, Errors: 3.
看上去这种做法并不好。 迁移正在运行,但是 DatabaseMigrations 没有调用数据填充。
没关系!我们把镜头转向 DatabaseTransactions :
# tests/Feature/UserTest.php
class UserTest extends TestCase
{
use DatabaseTransactions;
...
}
再次运行 phpunit
vendor\bin\phpunit
PHPUnit 5.7.17 by Sebastian Bergmann and contributors.
...... 6 / 6 (100%)
Time: 7.29 seconds, Memory: 14.00MB
OK (6 tests, 6 assertions)
好极了! DatabaseTransactions Trait 将每个测试的查询包装到事务中,因此来自上一个测试的数据不会影响后续测试。
小结
填充数据可以在测试中模拟许多不同的情况,轻而易举就能用大量的数据测试你的应用程序。
Talk is cheap, show me the code?
Less is more.
Achieve a better result with less effort.
And Remember Don't repeat yourself!
更多翻译的教程可以上 Laravel China 资讯站 查看哟~
本文教程翻译改编自 Sky Chin 的 Introduction to Seeding Data in Testing
本作品采用《CC 协议》,转载必须注明作者和本文链接
@JokerLinly 提一个小笔误,“我们先来试试 DatabaseMigrations:” 这句话下面的 code 里应该是
use DatabaseMigrations;
。@Macken 猴,已经修改好啦,顺便把相关的文档链接附上去了~:+1: