自転車とプログラミング

元自転車メーカーのマーケター、今は自社開発企業に勤めるエンジニアが主にプログラミングの話を書きます。

「Everyday Rails - RSpecによるRailsテスト入門」でRSpecを学ぶ 導入〜ファクトリのテクニック編

こんにちは、watanabeです。 RSpecの学習ブログの第二回です。

今回はファクトリ編のおまけです。データを作る際のテクニックだったり、DRYにする方法をまとめます。

同じクラスのファクトリを複数定義する

ファクトリは複数定義ができます。 同じクラス内で命名が異なるファクトリをクラスを指定して記述できます。

FactoryBot.define do
  factory :project do
    sequence(:name) { |n| "Project #{n}" }
    description { "A test project." }
    due_on { 1.week.from_now }
    association :owner

  # 昨日が締め切りのプロジェクト
  factory :project_due_yesterday, class: Project do
    sequence(:name) { |n| "Test Project #{n}" }
    description { "Sample project for testing purposes" }
    due_on { 1.day.ago }
    association :owner
  end
end

ファクトリの継承をつかう

親クラスのファクトリの中で、さらにファクトリを定義することで継承をつかうこともできます。 そのまま継承する値は特になにもせずともOKで、定義を変更する属性のみを記述します。

FactoryBot.define do
  factory :project do
    sequence(:name) { |n| "Project #{n}" }
    description { "A test project." }
    due_on { 1.week.from_now }
    association :owner

    # 今日が締め切りのプロジェクト
    factory :project_due_today do
      due_on { Date.current.in_time_zone }
    end
  end
end

traitで集合を表現する

trait ファクトリ名 do ~ endでファクトリの集合を表現できます。 定義は継承とほぼ同じでベースとなるファクトリに対して、異なる属性値のみを定義します。

FactoryBot.define do
  factory :project do
    sequence(:name) { |n| "Project #{n}" }
    description { "A test project." }
    due_on { 1.week.from_now }
    association :owner

    # 今日が締め切りのプロジェクト
    factory :project_due_today do
      due_on { Date.current.in_time_zone }
    end
  end

  # 締切が明日
  trait :due_tomorrow do
    due_on { 1.day.from_now }
  end

  # 昨日が締め切りのプロジェクト
  factory :project_due_yesterday, class: Project do
    sequence(:name) { |n| "Test Project #{n}" }
    description { "Sample project for testing purposes" }
    due_on { 1.day.ago }
    association :owner
  end
end

traitの場合はテストサイドでの呼び出し方法が変わり、FactoryBot.create( クラス名, trait名)で呼び出せます(※.createに限りません)。

コールバックをつかう

ファクトリにはbeforeやafterといったコールバックが用意されていて、オブジェクト生成の前後に実行する処理を定義できます。

FactoryBot.define do
  factory :project do
    sequence(:name) { |n| "Project #{n}" }
    description { "A test project." }
    due_on { 1.week.from_now }
    association :owner

    # メモ付きのプロジェクト
    trait :with_notes do
      after(:create) { |project| create_list(:note, 5, project: project)}
    end
end

この例の場合はベースとなるprojectオブジェクトが生成後に noteオブジェクトが生成されます。create_listは引数で指定したオブジェクトを指定した個数分生成します。 ブロックを取るので定義の処理はワンラインでも複数行でも記述できます。

DRYよりも可読性ではないかと

そんな感じでFactoryでデータを作る時の方法を書いてきました。今回はそのまま参考書から抜粋した内容が多いのでごくごく初歩的な方法ばかりです。 とはいえ、コールバックを多用したり、いくつもファクトリを定義したりすると容易に可読性は落ちてしまうと思います。テストはまずは通ること、そしてメンテナンスのコストが低いことが重要だと思いますのでDRYはほどほどに書けるといいんじゃないでしょうか。

教材はこちら

RSpec初心者の教材としては定番?の「Everyday Rails - RSpecによるRailsテスト入門」を使っています。 フィヨルドブートキャンプのメンター兼ソニックガーデンのエンジニアをされている伊藤淳一さんが訳をされています。 Rails7に対応修正した初心者〜中級者向けの内容となっているので信頼度が高いかなと思います。

leanpub.com