factory_girl の採用面接: 自分の Rails プロジェクトで採用する前に確認すること

factory_girlRuby on Rails (以下 Rails)におけるフィクスチャリプレースメントの代表格で,採用している人も多いのではないでしょうか.「使い方」に関するブログ記事も多いです.しかし,採用を見送った人がその理由,「なぜ使わなかったのか」について書いている記事はごくわずかです.そこで,本エントリでは,factory_girl を Rails プロジェクトで採用できるかどうかを判断するための確認ポイントを説明していきます.


factory_girl: まだ Fixtures 使っているんですか.そんなのよりモダンなフィクスチャリプレースメントを使ったほうが,...

そんなことは,本エントリの対象外です.

面接開始

それでは,factory_girl の機能を確認していきましょう.

モジュールでネストしたクラス名に対応していますか?


factory_girl: Factory 定義が面倒になりますが,頑張ります.

factory_girl では,ファクトリ定義で定義したファクトリ名がクラス名になります.ですので,Inventory::Item モデルのファクトリを定義しようとして,

FactoryGirl.define do
factory :inventory_item
title "you will be taller"
vendor_id 1
end
end

としても,

Inventory::ItemTest
test_factory_girl_can_handle_a_nested_class_name ERROR
uninitialized constant InventoryItem
[...]

と,:inventory_itemInventory::Item ではなく InventoryItem を探しに行ってしまうのでエラーになります.これを避けるには,ファクトリ定義でモジュール名を含めたクラス名を指定する必要があります.

FactoryGirl.define do
factory :inventory_item, class: Inventory::Item do
title "you will be taller"
vendor_id 1
end
end

モジュールのネスティングが長くなると,ファクトリ定義が面倒なことになるのはマイナス評価です.しかし,モジュールのネスティングが長くなっているときには,モジュールに不必要に複雑な名前をつけていたり,モジュール階層設計でしくじっていたりする場合が多いので,それほど重大ではありません.

一方向関連 (association) に対応していますか?


factory_girl: ごめんなさい,関連を使うときには belongs_to も設定してください.

factory_girl は belongs_to で定義された Rails の関連を扱うことができます.たとえば,"User has_many posts" な 2 つのクラス,UserPost:

# app/models/user.rb
class User < ActiveRecord::Base
has_many :posts
end

# app/models/post.rb
class Post < ActiveRecord::Base
belongs_to :user
end

があるときに,post ファクトリで,

FactoryGirl.define do
factory :post do
title "hello factory_girl"
user # ← ここ重要
end
end

のように user 関連を定義しておいてから,Factory(:post)とすると,Post モデルのオブジェクトだけでなく,それに関連する User モデルのオブジェクトも同時に作られます.たとえば

irb(main):001:0> Factory(:post).user.email
=> "foo@example.com"

とっても便利ですね.

しかし,factory_girl は内部で Post#user= メソッドを使って関連づけを行っているため,belongs_to が設定されていないモデルでは関連機能を使えません.ためしに Post クラスの belongs_toコメントアウトして Factory(:post) すると,

irb(main):001:0> Factory(:post)
NoMethodError: undefined method `user=' for #<Post:0x00000103c66868>

と,怒られました.「モデルに不要な関連を設定しない」というポリシーで開発している私にとっては,belongs_to 必須というのは大きなマイナスポイントです.

複合キーに対応していますか?


factory_girl: composite_primary_keys プラグインと使ってもらえれば対応できます.

面接官: いや,それ複合キーが主キーでないと使えないし,...

先程の一方向関連で説明したように,factory_girl の関連機能は Railsbelongs_to に依存しています.belongs_to が複合キーに対応していない以上,factory_girl も対応できないのです.唯一の望みとしては,Dr. NicComposite Primary Keys プラグインがあるのですが,名前のとおり,複合キーが主キーでないと使えません.

このプラグインでもう一つ問題なのが Rails バージョンへの追従です.Composite Primary Keys プラグイン自体は素晴らしい出来なのですが,Rails の最新バージョンへの対応は迅速ではないようで,Rails 2→3 のときにしばらく待たされました.頻繁に機能追加し,Rail の最新バージョンを積極的に追いかけていくプロジェクトでは Composite Primary Keys プラグインを使うのは難しいので,この案は却下です.

しかし,複合キーに対応していないのは fixtures も一緒なので,この点では factory_girl をマイナス評価しないことにします.

最後に fixtures でできなくて,factory_girl にできることを一つ答えてください.


factory_girl: Fixtures はデータベースにレコードを作るのでデータベース制約に違反する fixture を定義できませんが,私は,FactoryGirl.build でデータベースに保存される前のオブジェクトを作ることができます.

面接官: ありがとうございました.

面接終了

ここまで factory_girl のアラ探しをしてきましたが,正直,よくできたフィクスチャリプレースメントなので,使えないシーンを考えるのに苦労しました.もし,採用することができれば,fixtures よりも短いコードでテストデータを用意することでできるので,このエントリで挙げたポイントに不満が無ければ fixture の代替として factory_girl を検討してみてはいかがでしょう.

私の意見

私の意見を述べさせてもらえれば,factory_girl で fixtures を完全には置き換えず,部分的に活用するのが良いと思います.すなわち,fixtures と factory_girl の併用です.というのも,

  • Fixtures はデータベースの状態を用意するもの
    • データベースに制約がある場合,制約に違反する fixture を作成できない
    • 異常値を検査するネガティブテストには使えない
  • factory_girl は ActiveRecord オブジェクトを用意するもの
    • ActiveRecord で容易に実現できないデータの状態を作成できない
    • Rails の規約から外れると,とたんに使い勝手が悪くなる

という違いがあるからです.

それでは,factory_girl が活躍できる場所はどこかというと,「fixtures でできなくて,factory_girl にできること」で述べた「データベースに保存される前のオブジェクトを作る」必要があるテストです.すなわち,異常値をもつオブジェクトを作成し,モデルのバリデーションやデータベース制約をテストを行う場面です.このような場面では,factory_girl の継承 (inheritance) 機能が活躍するのですが,それは別の機会にお話ししようと思います.

2011-11-25 追記:
factory_girl の継承機能が活躍する例を書いてみました→ Rails の ActiveRecord モデルテストの書き方ガイドライン - passingloopの日記

この記事を書いたときの Rails のバージョン

  • Rails 3.1.3
  • factory_girl 2.3.0
  • factory_girl_rails 1.4.0