[rails]has_manyなフィクスチャを書くのに疲れたらFactory Girlがオススメ!

このエントリーを含むはてなブックマーク

factorygirl

フィクスチャはメンテナンスしづらい

書籍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で簡単にインストールできます。

  1. sudo gem install thoughtbot-factory_girl --source http://gems.github.com


config/environment.rbに依存するgemとして登録しておくと、テスト実行時に自動でFactory Girlがロードされるので便利です。

  1. config.gem "thoughtbot-factory_girl", :lib => "factory_girl", :source => "http://gems.github.com"


実際にサンプルを作ってみる

定義の継承やシーケンスといった機能も便利なのですが、とりあえず最大のメリットである複雑な関連をシンプルに定義できるという事を簡単なサンプルで証明したいと思います。

User,Page,Bookmarkという3つのモデルで簡単なブックマークアプリを作ります。

  1. # app/models/user.rb
  2. class User <ActiveRecord::Base
  3.   # name:string
  4.   has_many :bookmarks
  5.   has_many :pages, :through => :bookmarks
  6. end
  7.  
  8. # app/models/page.rb
  9. class Page <ActiveRecord::Base
  10.   # title:string
  11.   # url:string
  12.   has_many :bookmarks
  13.   has_many :users, :through => :bookmarks
  14. end
  15.  
  16. # app/models/bookmark.rb
  17. class Bookmark <ActiveRecord::Base
  18.   # user_id:integer
  19.   # page_id:integer
  20.   belongs_to :user
  21.   belongs_to :page
  22. end


UserとPageがBookmarkを中間テーブルとして多対多の関係になっています。

ファクトリーを定義する

Factory Girlは特定のクラスに対して名前を付けて雛形を定義することができます。

spec/factories.rbというファイルを作成し(rspecを使う場合)、次のように記述します

  1. Factory.define :bob, :class => User do |f|
  2.   f.name "bob"
  3. end
  4.  
  5. Factory.define :mike, :class => User do |f|
  6.   f.name "mike"
  7. end
  8.  
  9. Factory.define :yahoo, :class => Page do |f|
  10.   f.title "Yahoo!"
  11.   f.url "http://www.yahoo.com/"
  12. end
  13.  
  14. Factory.define :google, :class => Page do |f|
  15.   f.title "Google"
  16.   f.url "http://www.google.com/"
  17. 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)
  1. # spec/models/user_spec.rb
  2. require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
  3. describe User do
  4.   it "正常に保存できること" do
  5.     @user = Factory.build(:bob)
  6.     @user.save.should be_true
  7.   end
  8. end


テストを実行すると、ちゃんと動いているのがわかります。 Factory.buildで取得できるオブジェクトはスタブやモックではなく、れっきとしたモデルクラスのインスタンスなので当然saveなどのメソッドが使えます。

  1. User
  2. - 正常に保存できること
  3.  
  4. Finished in 0.067948 seconds
  5.  
  6. 1 example, 0 failures


関連を定義する

ここまでは普通のフィクスチャでも当然できますので、フィクスチャだったら面倒くさい多対多の関連を定義します。

bobが2つのブックマークを、mikeが1つのブックマークを持っていることをFactory Girlで書くにはどうすれば良いでしょうか?

そのためのテストコードを追加しておきましょう。

  1. it "ボブはブックマークを2つ持っていること" do
  2.   @bob = Factory(:bob)
  3.   @bob.should have(2).bookmarks
  4. end
  5.  
  6. it "マイクはブックマークを1つ持っていること" do
  7.   @mike = Factory(:mike)
  8.   @mike.should have(1).bookmarks
  9. end


テストが赤で失敗するのを確認したら、ファクトリーの定義を修正します。

  1. #spec/factories.rb
  2. Factory.define :bob, :class => User do |f|
  3.   f.name "bob"
  4.   f.pages {
  5.     [Factory(:google),Factory(:yahoo)]
  6.   }
  7. end
  8.  
  9. Factory.define :mike, :class => User do |f|
  10.   f.name "mike"
  11.   f.pages {
  12.     [Factory(:google)]
  13.   }
  14. end


これでOKです。簡単でしょ?どのユーザーがどのページを持っているか?直感的に書けることと、中間テーブルであるブックマークについて何も書かれていないことに注目してください。

それぞれのUserが持っているpagesを配列で指定しています。 この時、{}というブロックで渡すとFactoryGirlは遅延処理と見なしてくれるので、実際にpagesが呼ばれるまで定義は反映されません。

配列にはFactory(name)で、定義したファクトリーからインスタンスを生成します。

これでテストがグリーンになることを確認できました。

  1. User
  2. - 正常に保存できること
  3. - ボブはブックマークを2つ持っていること
  4. - マイクはブックマークを1つ持っていること
  5.  
  6. Finished in 0.152733 seconds
  7.  
  8. 3 examples, 0 failures


最後に

どうでしょうか?FactoryGIrl。 最近よく名前を聞くのに日本語で使い方を説明しているサイトが見つからなかったので、駆け足で使い方を説明してみました。

フィクスチャはやっぱり便利なところもあるので、使いたいのだけれど関連が絡んでくると一気にモチベーションが下がりますよね。 FactoryGirlはちょっと冗長だけれど、無理なく関連を管理できるので、これまでフィクスチャに悩まされてきた人にはおすすめです。

参考

Posted in ruby, ruby on rails, 日記 at 4月 26th, 2009. Trackback URI: trackback
Tags: , , ,

No Responses to “[rails]has_manyなフィクスチャを書くのに疲れたらFactory Girlがオススメ!”

Leave a Reply