fixtureの代換となるプラグインMachinistの使い方のメモ。下記URLのREADMEの意訳です。
notahat's machinist at master - GitHub
インストール
sudo gem install machinist --source http://gemcutter.org
セットアップ
spec/blueprints.rbに下記のように書きます。下記ではactive_record用のファイルをrequireしていますが、data_mapperやsequelなんかも使えるようです。
require 'machinist/active_record' require 'sham'
spec_helper.rbに下記のように書きます。(test_helper.rbの場合は省略)
require File.expand_path(File.dirname(__FILE__) + "/blueprints") Spec::Runner.configure do |config| //... config.before(:all) { Sham.reset(:before_all) } config.before(:each) { Sham.reset(:before_each) } //... end
Shamでattributeの値を生成する
modelのattributesに代入する値を生成するための方法としてShamがあります。
Sham.name { (1..10).map { ('a'..'z').to_a.rand }.join }
上記のようにブロックの戻り値に、name属性として扱いたい文字列を定義することができます。定義した文字列を生成したい場合は
Sham.name
のようにブロック無しで呼び出します。もちろんname以外の属性でも同じように使えます。
ブロック引数にはindex番号が入ります。
Sham.name {|index| "Name #{index}" }
Shamを使うメリット
テスト実行時に、毎回同じ値の連続を返してくれます。また、デフォルトでは生成した属性値が重複しません。重複した値を許す場合は下記のように:uniqueオプションを使います。
Sham.coin_toss(:unique => false) { rand(2) == 0 ? 'heads' : 'tails' }
FakerとShamの組み合わせ
Shamは、fakerという"それっぽい文字列"を返してくれるgemと組み合わせると非常に良い感じです。
Sham.name { Faker::Name.name }
複数の属性値を生成したい場合
下記のように、Sham.defineメソッドを使用すると、複数のShamの定義が一度に出来て楽です。
Sham.define do title { Faker::Lorem.words(5).join(' ') } name { Faker::Name.name } body { Faker::Lorem.paragraphs(3).join("\n\n") } end
blueprintメソッドによるオブジェクト生成
blueprintメソッドを利用して、どのような属性を持つオブジェクトを生成するかを定義します。blueprint(とSham)でモデルの属性の値をどうするか面倒見てくれるようになるので、テストに集中できるようになります。
blueprintの実行例
こんな感じで属性を定義します。
Post.blueprint do title { Sham.title } author { Sham.name } body { Sham.body } end
blueprintメソッドのブロック中でブロックを使わない場合、Machinistは属性名と同じ名前のShamの定義を自動的に探してくれるので、先ほどの定義は下記のように省略できます。
Post.blueprint do title author { Sham.name } body end
また、下記のように、設定した属性を同じブロック中で参照することが出来ます。
Post.blueprint do title author { Sham.name } body { "Post by #{author}" } end
makeメソッド
blueprintメソッド実行後に下記のようにmakeメソッドを使います。
Post.make
すると、
- Post.new
- blueprintで定義した属性を代入
- Post.save
という流れでレコードを作成してくれます。makeの引数にハッシュを渡すとblueprintで生成する値を上書きします。
Post.make(:title => "A Specific Title")
もしデータベースに保存したくない場合はmakeをmake_unsavedに置換します。make_unsavedは関連オブジェクトも保存しません。
Named Blueprints
下記のソース見ればわかると思うので略。
User.blueprint do name email end User.blueprint(:admin) do name { Sham.name + " (admin)" } admin { true } end User.make(:admin) # :adminの方が呼ばれる
Belongs_to associations
belongs_to や has_one(書いてなかったけどたぶん) で定義した関連オブジェクトを下記のように定義できます。
Comment.blueprint do post { Post.make } end
上記のコードを実行すると、Comment.makeしたときにPostも作られて、同時に二つのレコードが保存されます。
Machinistは関連を探して、どんなオブジェクトを作ればいいかを判別してくれるので、下記のように省略して書くことも可能です。
Comment.blueprint do post end
Postの属性値を上書きしたいときは下記のように書けます。
post = Post.make(:title => "A particular title") comment = Comment.make(:post => post)
other associations
has_many や has_and_belongs_to_many を定義しているオブジェクトは、関連オブジェクトが保存される前に保存される必要があります。なのでblueprintのブロック中にmakeを書く方法はとれません。
一番簡単な解決法は、下記のようなヘルパメソッドを定義することです。
def make_post_with_comments(attributes = {}) post = Post.make(attributes) 3.times { post.comments.make } post end
上記のコードを見るとわかりますが、makeメソッドはhas_manyな関連上でも使えます(DataMapperではサポート外)。
makeメソッドはブロックをとって、ブロック引数に生成したオブジェクトを渡すので、上記のコードは下記のようにも書けます。
def make_post_with_comments(attributes = {}) Post.make(attributes) do |post| 3.times { post.comments.make } end end
コントローラのテストでblueprintを使う
makeメソッドによく似たメソッドにplanメソッドがあります。違う点は、オブジェクトを保存せずに属性のハッシュ値を返すところです。このメソッドはコントローラのテストをするときに使えます。
test "should create post" do assert_difference('Post.count') do post :create, :post => Post.plan end assert_redirected_to post_path(assigns(:post)) end
planメソッドは関連オブジェクトを保存します。上記のコードでは、まず関連オブジェクトであるauthorを保存します。それからコントローラがauthor_id属性を期待していることを理解しているので、author_idをうまくコントローラに渡します。
planメソッドはhas_manyな関連にも使えます。ネストしたコントローラをテストするときに便利です。
test "should create comment" do post = Post.make assert_difference('Comment.count') do post :create, :post_id => post.id, :comment => post.comments.plan end assert_redirected_to post_comment_path(post, assigns(:comment)) end