意味のあるテストデータの作成
FactoryBotを使ったデータ作成について基礎を理解したいと思うことが多々ありました。
Everyday Rails Rspec 第4章、「意味のあるテストデータの作成」についてまとめる機会を頂いたので、こちらに下書きを書いていきたいと思います!
内容
・ファクトリ対フィクスチャ
・FactoryBotをインストールする
・アプリケーションにファクトリを追加する
・シーケンスを使ってユニークなデータを生成する
・ファクトリで関連を扱う
・ファクトリ内の重複をなくす
・コールバック
・ファクトリを安全に使うには
・まとめ
ファクトリ対フィクスチャ
フィクスチャのメリット
フィクスチャは比較的速い
Railsに最初から付いてくる
フィクスチャのデメリット
テストとは別のフィクスチャファイルに保存された値を覚えておく必要がある
フィクスチャはもろくて壊れやすい(らしい)
Railsはフィクスチャのデータを作るときActive Recordを読み込まない
・・・このような理由からテストデータのセットアップが複雑になってきたときはファクトリがおすすめ
ファクトリはシンプルで柔軟性に富んだテストデータ構築用のブロック
ファクトリは適切に(つまり賢明に)使えば、あなたのテストをきれいで読みやすく、リアルなものに保ってくれる
多用しすぎると遅いテストの原因になる(らしい)
FactoryBotをインストールする
group :development, :test do ~ end の中にgem 'factory_bot_rails'を追加し、bundle installします
group :development, :test do
略
gem 'factory_bot_rails'
略
end
アプリケーションにファクトリを追加する
spec/factorys/users.rbに属性や値を書いていきます
spec/factorys/users.rb
FactoryBot.define do
factory :user do
name 'hayashi'
email "test@gmail.com"
password '11111111'
end
end
このような設定をしておくだけで、テスト内で FactoryBot.create(:user) と書くと、定義したuserのデータが生成されます。
spec/models/user_spec.rb
require 'rails_helper'
describe 'User' do
it '有効なファクトリを持つ事' do
expect(FactoryBot.create(:user)).to be_valid
end
end
end
一方、FactoryBotを使用せず書くと、以下のようになります。
spec/models/user_spec.rb
require 'rails_helper'
describe 'User' do
it '有効なファクトリを持つ事' do
user = User.create(
name 'hayashi',
email "test@gmail.com",
password '11111111'
)
actexpect(:user).to be_valid
end
end
end
FactoryBotを使うと簡潔に書くことができる一方、どんな値を持ったデータが生成されているのかテストファイルを直接みるだけではわからないという注意点もあります。
時と場合によって、FactoryBotを使わずデータをベタ書きするのか、FactoryBotを使うのか使い分けるのが良いようです。
シーケンスを使ってユニークなデータを生成する
先ほど作ったファクトリを何回実行しても同じ値を持ったデータが作成されます。
メールアドレスのように、ユニークなデータでないとバリデーションエラーを起こしてしまう物に関しては、シーケンスを使ってユニークなデータを生成してあげることができます。
spec/factorys/users.rb
FactoryBot.define do
factory :user do
name 'hayashi'
sequence(:email) { |n| "test#{n}@gmail.com" }
password '11111111'
end
end
ファクトリで関連を扱う
FactoryBotはアソシエーションを扱うときにとても便利です。
以下のファクトリーはアソシエーション関連にあるデータも作成する設定です。
userモデル、projectモデル、noteモデルがあり、
・userモデルがhas_many :notes、has_many :projectsで、
・projectモデルがbelongs_to :user、has_many :notes
・noteモデルがbelongs_to :user、belongs_to :project
であるデータを作成する際は以下のように作成できます。
spec/factorys/notes.rb
FactoryBot.define do
factory :note do
message 'I am hayashi'
association :project
user {project.owner} // assosiation :user としてしまうと
end userが重複して作られてしまう
end
spec/factorys/projects.rb
FactoryBot.define do
factory :project do
sequence(:name) { |n| "project#{n}" }
association :owner
end
end
spec/factorys/users.rb
FactoryBot.define do
factory :user, aliases: [:owner] do
name 'hayashi'
sequence(:email) { |n| "test#{n}@gmail.com" }
password '11111111'
end
end
あとは、以下のように、FactoryBot.create(:note) とnoteを作成すればOKです。
spec/models/user_spec.rb
require 'rails_helper'
describe 'User' do
it '有効なファクトリを持つ事' do
expect(FactoryBot.create(:note)).to be_valid
end
end
end
ファクトリ内の重複をなくす
ファクトリで、ある属性の値だけ別なファクトリを作りたい時は以下のように設定します。
spec/factorys/projects.rb
FactoryBot.define do
factory :project do
sequence(:name) { |n| "project#{n}" }
due_on 1.week.from_now
association :owner
end
factory :project_due_yesterday, class: Project do
sequence(:name) { |n| "project#{n}" }
due_on 1.day.from_now
association :owner
end
end
こうすれば、
FactoryBot.create(:project)
FactoryBot.create(:project_due_yesterday)
とスペックでファクトリを呼び分けることで違うデータを持ったファクトリを生成することができます。
しかし、これだと、nameやassosiationの部分が重複しています。
この重複を無くした書き方も以下のようにすることで可能です。
spec/factorys/projects.rb
FactoryBot.define do
factory :project do
sequence(:name) { |n| "project#{n}" }
due_on 1.week.from_now
association :owner
factory :project_due_yesterday do // ネストさせた
due_on 1.day.from_now
end
end
end
factory :project の中に factory :project_due_yesterday を入れ子にすることで、重複していたnameやassosiationの記述、class: Projectの記述を省略できます。
また、ファクトリをネストさせる他にも、traitを使った書き方もあります。
spec/factorys/projects.rb
FactoryBot.define do
factory :project do
sequence(:name) { |n| "project#{n}" }
due_on 1.week.from_now
association :owner
trait :due_yesterday do
due_on 1.day.from_now
end
end
end
traitを用いて書いた時、スペックでファクトリを生成する際は、
FactoryBot.create(:project, :due_yesterday)
と引数でtraitを指定します。
コールバック
あるモデルをcreate・newした後、別のモデルも作成したい場合はコールバックを使用します。
コールバックをtraitを用いて書けば、簡単にコールバックを呼んだり呼ばなかったり選択することができます。
spec/factorys/projects.rb
FactoryBot.define do
factory :project do
sequence(:name) { |n| "project#{n}" }
due_on 1.week.from_now
association :owner
trait :with_note do
after(:create){|project| create_list(:note, 5, project: project)}
end
end
end
こうしておくと、FactoryBot.create(:project, with_note) とすればコールバックが実施され、projectの関連先のnoteのデータも作成してくれます。
FactoryBot.create(:project) と通常通りならコールバックは実施されません。
これ以外にも、traitを使えば非常に複雑なデータの作成もスッキリかけるようになります。
ファクトリを安全に使うには
ファクトリを使うとテスト中に予期しないデータが作成されたり、無駄にテストが遅くなったりする原因になります。
(コールバックを使って関連するデータを作成する必要があるなら、ファクトリを使うたびに呼び出されないよう、トレイトの中でセットアップするようにするなど気をつけます。)
まとめ
・ファクトリを使うことで、複雑なデータでもスペック内でスッキリと呼び出すことが可能になります。
・テストは理解しやすいものであることが大切なので、初心者の場合、ファクトリではなくnewやcreateでベタ書きするのもありだよ、と翻訳者の1人である魚さんも言っていました。
・ファクトリの便利さを知れたので、まずは使い方をもっと理解し、パフォーマンスや可読性についても考慮してかけるようになりたいです。