技術書典2 に行ったら無性に本を書きたくなったけど、本書くのは 面倒 大変です。
というわけで、とりあえずブログに記事を1つ書いてみた。
factory_girl
factory_girl はテスト用データを作成するときに使う gem です。
下記は User のモデルを定義するファクトリーです。
FactoryGirl.define do
factory :user do
first_name "John"
last_name "Doe"
admin false
end
end
このファクトリーから User のテストデータを生成することができます。
FactoryGirl.create(:user)
factory_girl
は関連先のレコードを自動生成したり、連番を生成したり、結構多機能だったりします。
このあたりの詳細を知りたい方は公式の README を読むか、英語が苦手なら日本語の紹介記事などを読むと良いです。
錆びついたファクトリー
ここからが本題で、factory_girl
で定義したファクトリーは時間が経ったり、作成者が未熟だと 錆びる (=ビルドしづらい、できなくなる)ことがあります。
錆びついたファクトリー例を紹介してみたいと思います。
2回以上ビルドできない
class User
validates :nickname, uniqueness: true
end
FactoryGirl.define do
factory :user do
nickname "sinsoku"
end
end
これはひどい。 uniqueness 制約により2回目のビルドは失敗します。
FactoryGirl.define do
factory :user do
sequence(:nickname) { |n| "nickname_#{n}" }
end
end
上記のように sequence を使って回避すべきです。
build_stubbed なのにレコードが生成される
クラスの属性だけでテストができる( DB アクセスが不要な)ケースがあります。
class Book
def published?
published_at <= Time.current
end
end
このようなメソッドのテストでは DB アクセスをしないように build_stubbed
を使うべきです。*1
しかし、ファクトリーが下記のような定義だとレコードが生成されてしまいます。
FactoryGirl.define do
factory :book do
author { create(:user) }
published_at '2017-04-10'
end
end
これは association を使って定義すべきです。
FactoryGirl.define do
factory :book do
association :author, factory: :user
published_at '2017-04-10'
end
end
association を使うことで、 build_stubbed
の時にレコードが生成されなくなります。
デフォルトのファクトリーが重い
FactoryGirl.define do
factory :user do
name 'serval'
transient do
friends_count 10
end
after(:create) do |_user, evaluator|
create_list(:user, evaluator.friends_count)
end
end
end
デフォルト値のフレンズが多すぎます><
FactoryGirl.define do
factory :user do
name 'serval'
factory :user_with_friends do
transient do
friends_count 10
end
after(:create) do |_user, evaluator|
create_list(:user, evaluator.friends_count)
end
end
end
end
デフォルトのファクトリーはシンプルに保ち、フレンズの多いファクトリーは別にしておきましょう。
条件の必要なファクトリー
class Servant
belongs_to :master
validate :must_be_valid_master
TEAM = {
'Artoria Pendragon' => 'Shirou Emiya'
}
private
def must_be_valid_master
errors.add(:master, ' is invalid') unless TEAM[name] == master.name
end
end
FactoryGirl.define do
factory :servant do
association :master, factory: :user
name 'Artoria Pendragon'
end
end
このファクトリーは master が ‘Shirou Emiya’ のときだけビルドできます。
user = FactoryGirl.create(:user, name: 'Shirou Emiya')
servant = FactoryGirl.create(:servant, master: user)
このように、ビルド時に必要な関連を渡すようなファクトリーは使いづらいです。
FactoryGirl.define do
factory :servant do
association :master, factory: :user, name: 'Shirou Emiya'
name 'Artoria Pendragon'
end
end
FactoryGirl.create(:servant)
だけでビルドが出来るようにしておくべきです。
その他
時間の経過によりバリデーションが追加・変更されたり、モデルの関連が変わってしまい、ファクトリーが錆びてしまうことがあります。
幸い factory_girl
には Lint がついています。
namespace :factory_girl do
desc "Verify that all FactoryGirl factories are valid"
task lint: :environment do
if Rails.env.test?
begin
DatabaseCleaner.start
FactoryGirl.lint
ensure
DatabaseCleaner.clean
end
else
system("bundle exec rake factory_girl:lint RAILS_ENV='test'")
end
end
end
上記のような rake タスクを作成し、 CI で常にファクトリーがビルドできるかチェックしておくのが良いでしょう。
おわり
パッと思い出せた factory_girl
に関するアンチパターンを書いてみました。
他のアンチパターンはやる気と時間があれば書く…かも?