Laravelのfactoryのinsertを改善してテストの効率化をした
2019.12.24
Laravelのfactoryによるデータ作成を効率化させて、seederで時間がかかっていた部分を改善した内容について書きました。
環境
- Laravel 5系
Laravelのfactoryは強力で、階層構造を持ったデータも直感的な記述で作成することができます。ですが、1件ずつinsertが発行されてしまう為、少し注意が必要です。
// usersを5件つくる
factory(User::class, 3)
->create()
->each(function (User $user) {
// ordersを5件つくる
factory(Order::class, 5)
->create(['user_id' => $user->id])
->each(function (Order $order) {
// order_detailsを10件つくる
factory(OrderDetail::class, 10);
});
});
このコードでは、3×5×10で150回のinsertが作成されてしまい、パフォーマンス的にあまりよろしくありません。
このような場合、factoryにはinstanceのみ生成するmakeという関数があるので、これをinsertと合わせて使います。
// usersを3件追加
$users = factory(User::class, 3)->make();
User::query()->insert($users->toArray());
//insertしたuserをfind
$users = User::query()->orderBy('created', 'desc')->limit(3)->get();
$users->each(function (User $user){
// ordersを5件作成
$orders = factory(Order::class, 5)->make(['user_id' => $user->id]);
Order::query()->insert($orders->toArray());
//insertしたorderをfind
$orders = Order::query()->orderBy('created', 'desc')->limit(5)->get();
$orders->each(function(Order $order){
// order_detailsを10件作成
$orderDetails = factory(OrderDetail::class, 10)->make([
'user_id' => $user->id, 'order_id' => $order->id
]);
OrderDetail::query()->insert($orderDetails->toArray());
});
});
コードの見通しが悪くなるのが悔しいですが、SQLの実行回数は150回から、23回(1+1+3*(2+5*(1)))まで減らすことができました。
上のコードを書いた所、「この方が少なくなるよ」とレビューをいただきました。以下のコードでは、SQLの実行回数は合計5回になります。
<?php
// Userを3件作成
$users = factory(User::class, 3)->make();
User::query()->insert($users->toArray());
$users = User::query()
->orderBy('created', 'desc')
->limit(3)
->get();
// Userに紐づくOrdersを作成
$orders = [];
$users->each(function (User $user){
$orders[] = factory(Order::class, 5)
->make(['user_id' => $user->id])
->toArray();
});
Order::query()->insert($orders);
// 作成した、Userに紐づくOrderを取得をして、
// そのOrderに紐づくOrderDetailを作成
$orderDetails = []
$orders = Order::query()
->whereIn('user_id', $users->pluck('user_id')
->toArray())
->get();
$orders->each(function(Order $order){
$orderDetails[] = factory(OrderDetail::class, 10)
->make(['user_id' => $user->id, 'order_id' => $order->id])
->toArray());
});
OrderDetail::query()->insert($orderDetails);
また、make関数は、配列で準備しているデータをfacotryを通してinsertしたい場合にも使うことができます。
$vegetables = [
['vegetable_group' => 1, 'name' => '大根'],
['vegetable_group' => 1, 'name' => 'かぼちゃ'],
['vegetable_group' => 1, 'name' => 'さつまいも'],
['vegetable_group' => 2, 'name' => 'にら'],
['vegetable_group' => 2, 'name' => 'レタス'],
...
...
['vegetable_group' => 9, 'name' => 'えのき'],
['vegetable_group' => 9, 'name' => 'しめじ'],
];
$insertVegetables = [];
foreach($vegetables as $v){
$insertVegetables[] = factory(Vegetables::class)->make($v)->toArray();
}
Vegetables::query()->insert($insertVegetables);
さいごに
テストの実行に時間がかかっているプロジェクトがあり、改善したい思いチューニングした内容を記事にしました。大規模なプロジェクトはどんな風にテストデータ設計しているか知りたいなと思いました。