分享

4. 使用Model Factories快速生成测试数据

 鸟哥软件 2017-05-03

我们在开发的时候经常会需要修改已经写好的创建表的migration文件,但是又不想在开发时就写太多的修改或者添加表字段的migration文件,导致migration文件过多,而如果直接修改了已经创建好的migration文件,那就必须要migrate:refresh了,但是表中的测试数据重新添加又是件麻烦的事,这时候我们就可以使用laravel的数据填充功能Seeder, 在讨论这个Seeder之前,我们先来看下如何批量生成测试数据。

laravel框架默认引入了fzaninotto/Faker包,这个包能让我们快速的生成一些测试数据。包的开发思路基于Perl的Data::Faker和ruby的faker。这个包的具体使用方式在这里https://github.com/fzaninotto/Faker,我们来看下在Laravel中如何使用它.

我们去跑一个Laravel 5.3的框架,并且建立好数据库,并执行掉php artisan migrate命令。这里自己做,大家应该是可以闭着眼睛做好这些了。

所有的model factories我们都会放在database/factories/ModelFactory.php中,我们打开它:

<?php

/*
| Model Factories 
|
| Here you may define all of your model factories. Model factories give
| you a convenient way to create models for testing and seeding your
| database. Just tell the factory how a default model should look.
| 你可以在这里定义你所有的模型工厂,使用模型工厂可以让我们在单元测试的时候很方便的使用生成
| 的测试数据,同时也可以快速的将测试数据保存到我们的数据库中。
*/

$factory->define(App\User::class, function (Faker\Generator $faker) {
    static $password;

    return [
        'name' => $faker->name,
        'email' => $faker->unique()->email,
        'password' => $password ?: $password = bcrypt('secret'),
        'remember_token' => str_random(10),
    ];
});

默认的已经有了一个为User模型生成测试数据的方法,这里的$faker->name,$faker->unique()->email都是fzaninotto/Faker包提供,上面已经给了它的github地址,大家用的时候自己去查找下就行。

怎么使用它呢? 我们打开php artisan tinker, 执行

 factory(App\User::class)->make();

这句话的意思是,我们会生成一个User对象,但是我们会使用模型工厂全局帮助函数factory()来生成它,生成的时候就给对象的所有属性赋值。结果如下:

Psy Shell v0.8.0 (PHP 7.0.12 — cli) by Justin Hileman
>>> factory(App\User::class)->make();
=> App\User {#692
     name: "Candace Bins",
     email: "morar.ethan@gmail.com",
   }
>>> factory(App\User::class)->make();
=> App\User {#698
     name: "Prof. Alf Graham MD",
     email: "mpagac@yahoo.com",
   }

每调用一次都会生成一个具有不同属性值的对象,那如果我们要同时生成多个这样的对象呢?比如我们要生成500个User对象,我们只要传入第2个参数即可,如下:

factory(App\User::class, 500)->make();

通常我们会需要将这些生成的数据保存到数据库,那使用create()即可

factory(App\User::class, 2)->create();

不过我们一般就不跑到tinker中去生成这样测试数据了,我们将这条语句些到seeds/DatabaseSeeder.phprun方法中:

    public function run()
    {
        // 清空users表中已经存在的数据
        User::truncate();

        factory(User::class, 2)->create();
    }

然后执行:

php artisan db:seed

就会调用上面这个run()方法,执行当中的语句了。

不过在正式开发的时候,$faker数据要依据你的具体情况来用了,比如我们会需要一些真实的数据,比如说一些字典表,那必须是正确的数据,那就会手动指定了,所以通常我们对应每个模型都会去写一个Seeder类,然后在DatabaseSeeder.php中去调用它们,在具体的Seeder类中去调用模型工厂,这样代码会方便管理很多,因为现在还没有讲到Seeder类,所以我们现在知道怎么用模型工厂就可以了。

对于模型工厂的数据,我们可能更多的还是用在测试中的(测试以后最后再详说),我们看下测试中怎么用:

我们去建立一个posts表,migration文件如下:

    public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->increments('id');
            $table->string('title');
            $table->text('body');
            $table->timestamps();
        });
    }

比如我们要测试这个流程: 当我访问post的路由的时候,给我显示数据库中的post的标题和内容.

我们直接改下tests/ExampleTest.php中的代码:

<?php

use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use App\Post;

class ExampleTest extends TestCase
{
    // 不让测试的数据保存到数据库中,没有这条语句,当执行phpunit时,会
    // 在posts表中不断的插入数据,自己测试下
    use DatabaseTransactions;
    /**
     * A basic functional test example.
     *
     * @return void
     */
    public function testBasicExample()
    {
        // 通过模型工厂生成带有测试数据属性的$post对象
        $post = factory(Post::class)->create();

        $this->visit('posts')
             ->see($post->title);
    }
}

database/factories/ModelFactory.php中定义模型工厂:

$factory->define(App\Post::class, function (Faker\Generator $faker) {
    return [
        'title' => $faker->sentence,
        'body' => $faker->paragraph
    ];
});

编写路由文件,laravel 5.3路由分的很细,有针对api的,针对web的,还有针对命令行的,针对web的在routes/web.php中( 路由这样改个人觉得很好,比5.2好多了)

Route::get('posts', function() {
  return view('posts')->with('posts', App\Post::all());
});

然后去弄个resources/views/posts.blade.php视图

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>zhoujiping.com</title>
</head>
<body>
    @foreach ($posts as $post)
        <article>
            <h2>{{ $post->title }}</h2>
            <div class="body">{{ $post->body }}</div>
        </article>
    @endforeach
</body>
</html>

到命令行执行phpunit, 是绿色的QQ20161221-0.png

我们在回头来说下fzaninotto/Faker包, 默认我们生成的测试数据都是英文的,如果你一定要想生成中文的测试数据(其实没啥用),该怎么生成?回到database/factories/ModelFactory.php

fzaninotto/Faker源码中我们可以看见fzaninotto/Faker的包中的关于语言版本相关的都放在Provider中:
1.png

而在laravel中$faker是Faker\Generator的是一个实例,在Faker\Generator中有这样一个方法:

    public function addProvider($provider)
    {
        array_unshift($this->providers, $provider);
    }

这样一看就明白了,将需要的语言文件依赖注入进来就可以了。

那我们来改下代码:

$factory->define(App\User::class, function (Faker\Generator $faker) {
    $faker->addProvider(new Faker\Provider\zh_CN\Person($faker));

    static $password;

    return [
        'name' => $faker->name,
        'email' => $faker->unique()->email,
        'password' => $password ?: $password = bcrypt('secret'),
        'remember_token' => str_random(10),
    ];
});

我们到tinker中跑一下:
2.png

现在生成的姓名就是中文的了,但是该组件对中文的支持类太少,所以你可能会在写Seeder的时候用一部分$faker,用一部分手动指定,或者自己写一些数组,随机插入也可以。

本节到这里结束。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多