自転車とプログラミング

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

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

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

今回はファクトリ編です。minitestでいうところのフィクスチャ、テスト用のデータを作る部分になります。

ファクトリの導入

factory_bot_railsをGemfileに追加して$ bundle installすれば使えるようになります。

データを作る

$ bin/rails g factory_bot:xxx yyyを実行するとファクトリがgenerateされます。 xxxはモデル等のデータの種類、yyyはモデルであればモデル名(user など)を入れる形です。

everydayrails-rspec-jp-2024 nabeyu$ bin/rails g factory_bot:model user
      create  spec/factories/users.rb

factories/users.rbを覗いてみます。

FactoryBot.define do
    factory :user do
  end
end

初期状態ではこのようにデータは空の状態です。 モデルに対してほしい属性を 属性名 { データの内容 } と記述していくとファクトリを作ることができます。

FactoryBot.define do
    factory :user do
      first_name { "Aaron" }
      last_name  { "Sumner" }
      email { "tester@example.com" }
      password { "dottle-nouveau-pavilion-tights-furze" }
  end
end

これで準備完了です。 テスト内で FactoryBot.create(:user)で新しいuserオブジェクトが生成されてテストDBに永続化されます。また、 FactoryBot.build(:user)と書くと新しいオブジェクトが生成されてメモリ内に保存されます。

フィクスチャとファクトリの比較

フィクスチャは - yamlで記述する - 比較的速い - 複雑なフィクスチャはメンテナンスコストが高い - フィクスチャはデータをDBに読み込ませる際にActive Recordを介さない(つまり、バリデーション等の機能しない)

フィクスチャは本番環境のデータとの不整合が起こる可能性があるため、その点だけをとってもファクトリのメリットは大きいです。

必ずしもファクトリを使う必要はない

ファクトリは便利な機能ですが、別ファイルにオブジェクトの生成内容を記述するという性質上、そのオブジェクトの内容はテストそのものを見てもわからない場合があります。これではテストはDRYになりますが、可読性は下がってしまいますのでテスト内でオブジェクトを生成する処理を書くことも選択肢として残していくことが必要です。 この作るRubyオブジェクトを plain old Ruby objects(PORO)と言うそうです。

シーケンスでユニークなデータを作る

複数のユーザーが必要なシーンは多そうですが、その場合はどうしたらいいでしょうか? モデルに一意のバリデーションが設定されている場合、ユーザーを2つ以上createするとエラーが発生します。バリデーションのある属性はユニークにして生成しなければなりません。

複数のオブジェクトをユニークに作成するには「シーケンス」を使います。

FactoryBot.define do
    factory :user do
      first_name { "Aaron" }
      last_name  { "Sumner" }
      sequence(:email) { |n| "tester#{n}@example.com" }
      password { "dottle-nouveau-pavilion-tights-furze" }
  end
end

seequence(属性名) { ブロック }でシーケンスを 定義してブロック内の返り値をファクトリとして生成します。このときにブロック変数が生成するたびに+1される数値になっていて、ブロック内でブロック変数を使って返り値を定義することでユニークな値を取得できるようになります。

関連を表現する

関連の表現はassociationで可能です。 インスタンスの生成において依存する要素があるときに、関連する要素を作りながらオブジェクトを生成する機能です。

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

ownerとはuserのaliasです。 projectはuserと一対多の関係になるオブジェクトです。 生成する際にassociationと入っていることでオブジェクトの生成時点で、関連する要素の作製も可能です。

教材はこちら

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

leanpub.com