如何利用数据填充做好测试

file
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 ChinIntroduction to Seeding Data in Testing

本作品采用《CC 协议》,转载必须注明作者和本文链接
Stay Hungry, Stay Foolish.
本帖由系统于 5年前 自动加精
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 3

@JokerLinly 提一个小笔误,“我们先来试试 DatabaseMigrations:” 这句话下面的 code 里应该是 use DatabaseMigrations;

6年前 评论

@Macken 猴,已经修改好啦,顺便把相关的文档链接附上去了~:+1:

6年前 评论
public function test_follows()
{
    $userA = User::find(2);
    $userB = User::find(3);

    $userA->follows($userB);
// 这里不是 1 ??
    $this->assertEquals(2, $userA->following()->count());
}
6年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!