28. FactoryGirl を fixtures のように使う

モデルのテストを行うのにさまざまな条件の下でテストデータを作ることが必要になる。以前は、さまざまなテスト条件に合わせて、大量の fixtures を用意していた。しかしながら、テストデータをすべてのテストに適合するように準備することは非常に難しい。これを回避するものとして最近は FactoryGirl が使われるようになった。

以前も書いたように FactoryGirl はプログラムでテストデータを作成するので、テストごとに条件に従ったデータのみを作成することができる。また、association や after(:build) を駆使することである程度の関連のテストはできる。しかしながら、fixtures で作り込まれたような、関連が複雑に絡み合ったデータを毎回うまく作ることは難しかった。

そこで、fixtures のようにキーで特定のデータを簡単に作ることができ、FactoryGirl のように必要に応じてデータを作成できる仕組みを作成してみた。

find_or_create の作成

Rails 4 では find_or_create_by という便利なメソッドが用意されている。しかしながら、これは実装に使うには便利だが、create 時には実際のデータをつくってしまい、FactoryGirl の継承などの美味しい機能が使えない。そこで、find_or_create_by と同様な仕組みである find_or_create メソッドを spec/support/factory_girl.rb に追記した。ベースはGistHubGistに紹介されているものであるが、一部気に入らない部分を修正している。

FG = FactoryGirl

# @see https://gist.github.com/thewatts/dcc91ef40b144aff42ae
module FactoryGirl::Syntax::Methods
  def find_or_create(name, attributes = {}, &block)
    factory = FG.factory_by_name(name)
    klass   = factory.build_class

    factory_attributes = FG.attributes_for(name)
    attributes = factory_attributes.merge(attributes)

    result = klass.find_by(attributes, &block)

    result || FG.create(name, attributes, &block)
  end
end

この結果、同じ属性を持った factory が複数作られなくなる。特に unique 属性があるモデルの関連を作るときにデータが二重化されてエラーになることを防げる。

year_factory メソッドの作成

上記の find_or_create は実際には直接的に使わず、モデルごとの factory_creater メソッドを用意する。以前作成した年度(Year)のための year_factory メソッドを spec/support/create_factories/year_factory.rb に作成する。

YearFactoryHash = {
  year: %w( 2014 t ),
  pre_year: %w( 2013 f ),
  post_year: %w( 2015 f ),
}

# @param [Symbol] key オブジェクトを一意に決定するキー
# @return [Year] Year FactoryGirl オブジェクト
def year_factory(key)
  y, d = YearFactoryHash[key.to_sym]
  FG.find_or_create(
    :year,
    year: y.to_i,
    default_year: d == 't'
  ) if y
end

このメソッドにより、year_factory :year とすれば現在の年度が、year_factory :post_year とすれば次年度が取得できる。同一テストの中では、find_or_create によって同じキーからは同じオブジェクトが取得できる。こうしてしまえば fixtures の時と同様にテストを記述できることになる。関連が絡むものについてはまた、後日説明する。spec/factories/year.yml はデフォルト属性と後処理のみを残して全て削除する。この辺りは実際の例を交えて後日説明する。

Guardfile の修正

デフォルトの設定では、spec/support 以下のファイルが変更されるとすべてのテストが実行されてしまう。create_factories の下はかなり頻繁に変更されるので、Guardfile を以下の様にコメント化して、全テストの対象からは外してしまった。

  #watch(rspec.spec_support) { rspec.spec_dir }

一方で、year_factory.rb が修正されたときには、関連するモデルやコントローラについてはテストを実行して欲しい。このため、Guardfile に以下のテスト実行を追加した。

  watch(%r{^spec/support/create_factries/(.+)\.rb}) do |m|
    [
      rspec.spec.("models/#{m[1].gsub(/_factory/, '').singularize}"),
      Dir[File.join("**/#{m[1].gsub(/_factory/, '').pluralize}_controller_spec.rb")][0]
    ]
  end

これで *_factory.rb を作成すると自動で model や controller のテストが走るようになる。

今日はここまで。

written by iHatenaSync