我们在开发的时候经常会需要修改已经写好的创建表的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.php
的run
方法中:
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
, 是绿色的
我们在回头来说下fzaninotto/Faker
包, 默认我们生成的测试数据都是英文的,如果你一定要想生成中文的测试数据(其实没啥用),该怎么生成?回到database/factories/ModelFactory.php
在fzaninotto/Faker
源码中我们可以看见fzaninotto/Faker
的包中的关于语言版本相关的都放在Provider中:

而在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中跑一下:

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