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

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.

本帖由系统于 1年前 自动加精
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
讨论数量: 3

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

3年前 评论

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

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

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

请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!