
フィクスチャはメンテナンスしづらい
書籍The Rails Wayの中には「皆フィクスチャが嫌い」という項目があります(次の項目は「フィクスチャはそれほど悪くない」ですが)。
フィクスチャが嫌われる最大の原因は「メンテナンスが難しい」という事でしょう。
中間テーブルを必要とする多対多の関連をフィクスチャにしようと思ってうんざりした経験は誰にもでもあると思います。
Fixtureのhas_manyでこんな書き方したい
http://www.func09.com/wordpress/archives/369
フィクスチャの代わりにFactoryGirlを使ってみる
Factory GirlはRailsのテストにおいてfixtureの代替となるツールです。
つまりテスト時のモデルデータを用意するための仕組みです。
Factory Girlはフィクスチャと違い、Rubyのスクリプトで直接データを定義していきます。
特徴としては
- Rubyコードで定義(YAMLやCSVではない)
- 関連のメンテナンスが楽
- 定義の継承もできる
- 同じ定義から連続的なデータを生成できる(シーケンス)
Rubyコードで実データを作る感覚でテストデータを定義するので、フィクスチャと違い中間テーブルのメンテナンスから解放されます。
インストール
インストールはgemで簡単にインストールできます。
config/environment.rbに依存するgemとして登録しておくと、テスト実行時に自動でFactory Girlがロードされるので便利です。
実際にサンプルを作ってみる
定義の継承やシーケンスといった機能も便利なのですが、とりあえず最大のメリットである複雑な関連をシンプルに定義できるという事を簡単なサンプルで証明したいと思います。
User,Page,Bookmarkという3つのモデルで簡単なブックマークアプリを作ります。
class User <ActiveRecord::Base
# name:string
has_many :bookmarks
has_many :pages, :through => :bookmarks
end
# app/models/page.rb
class Page <ActiveRecord::Base
# title:string
# url:string
has_many :bookmarks
has_many :users, :through => :bookmarks
end
# app/models/bookmark.rb
class Bookmark <ActiveRecord::Base
# user_id:integer
# page_id:integer
belongs_to :user
belongs_to :page
end
UserとPageがBookmarkを中間テーブルとして多対多の関係になっています。
ファクトリーを定義する
Factory Girlは特定のクラスに対して名前を付けて雛形を定義することができます。
spec/factories.rbというファイルを作成し(rspecを使う場合)、次のように記述します
f.name "bob"
end
Factory.define :mike, :class => User do |f|
f.name "mike"
end
Factory.define :yahoo, :class => Page do |f|
f.title "Yahoo!"
f.url "http://www.yahoo.com/"
end
Factory.define :google, :class => Page do |f|
f.title "Google"
f.url "http://www.google.com/"
end
specもしくはtestディレクトリにfactories.rbというファイルか、factories/というディレクトリ内にrbファイルを配置すれば、自動的にFactoryGirlで定義が利用可能となります。
FactoryGirlはFactory.define(name,options)メソッドでテスト用のモデルインスタンスを作成する工場を定義していきます。
nameにはユニークな定義名を、optionsにはHashを渡します。とりあえず:classオプションがモデルクラスを指定することだけ覚えておきましょう。
ブロック引数に渡したfはfactoryインスタンスです。モデルに指定したattributeの雛形を定義しておきます。
ここではbobとmikeというユーザー、yahooとgoogleというページを定義しました。
テストから利用する
では実際にテストコードからFactoryGirlを利用してみます。 先ほど定義したファクトリーを使ってインスタンス化する場合は
- Factory.create(name)
- Factory.build(name)
を使います。createは保存したインスタンスを、buildは保存前のインスタンスを返すようになっていて、createはデフォルトの場合、次のようなショートハンドが使えます。
- Factory(name)
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
describe User do
it "正常に保存できること" do
@user = Factory.build(:bob)
@user.save.should be_true
end
end
テストを実行すると、ちゃんと動いているのがわかります。 Factory.buildで取得できるオブジェクトはスタブやモックではなく、れっきとしたモデルクラスのインスタンスなので当然saveなどのメソッドが使えます。
- 正常に保存できること
Finished in 0.067948 seconds
1 example, 0 failures
関連を定義する
ここまでは普通のフィクスチャでも当然できますので、フィクスチャだったら面倒くさい多対多の関連を定義します。
bobが2つのブックマークを、mikeが1つのブックマークを持っていることをFactory Girlで書くにはどうすれば良いでしょうか?
そのためのテストコードを追加しておきましょう。
@bob = Factory(:bob)
@bob.should have(2).bookmarks
end
it "マイクはブックマークを1つ持っていること" do
@mike = Factory(:mike)
@mike.should have(1).bookmarks
end
テストが赤で失敗するのを確認したら、ファクトリーの定義を修正します。
Factory.define :bob, :class => User do |f|
f.name "bob"
f.pages {
[Factory(:google),Factory(:yahoo)]
}
end
Factory.define :mike, :class => User do |f|
f.name "mike"
f.pages {
[Factory(:google)]
}
end
これでOKです。簡単でしょ?どのユーザーがどのページを持っているか?直感的に書けることと、中間テーブルであるブックマークについて何も書かれていないことに注目してください。
それぞれのUserが持っているpagesを配列で指定しています。 この時、{}というブロックで渡すとFactoryGirlは遅延処理と見なしてくれるので、実際にpagesが呼ばれるまで定義は反映されません。
配列にはFactory(name)で、定義したファクトリーからインスタンスを生成します。
これでテストがグリーンになることを確認できました。
- 正常に保存できること
- ボブはブックマークを2つ持っていること
- マイクはブックマークを1つ持っていること
Finished in 0.152733 seconds
3 examples, 0 failures
最後に
どうでしょうか?FactoryGIrl。 最近よく名前を聞くのに日本語で使い方を説明しているサイトが見つからなかったので、駆け足で使い方を説明してみました。
フィクスチャはやっぱり便利なところもあるので、使いたいのだけれど関連が絡んでくると一気にモチベーションが下がりますよね。 FactoryGirlはちょっと冗長だけれど、無理なく関連を管理できるので、これまでフィクスチャに悩まされてきた人にはおすすめです。
参考
Tags: factorygirl, rails, rspec, ruby