Just do IT

思うは招く

FactoryBot The Right Wayの感想 #kaigionrails2020

Kaigi on Rails 2020での学びや、感想をつらつらと書いていく。

今回は、メドピア(MedPeer)のtoshimaruさんによる、FactoryBotの正しい使い方について。

動画はこちら。

www.youtube.com

なぜFixtureが重要なのか?

  • テストの前提が間違えばその後のテストも破綻するから
  • テストごとに何度も繰り返し呼ばれるので、非効率な書き方をするとパフォーマンスが落ちる

FactoryBotを使うときに意識すべきこと

  • Factoryのデフォルト定義はミニマルなデータセットになっているか?
  • 各々のテストケースで使っているFactoryは最低限の前提状態をつくりだせているか?

RailsでFactoryBotを使う

# Railsの場合
group :test do
    gem "factory_bot_rails"
end

FactoryBot.createFactoryBot を省略する設定

# rails_helper.rb
RSpec.configure do |config|
    config.include FactoryBot::Syntax::Methods
end

# FactoryBot.create(:user)
create(:user)

巨大なデフォルトデータになっている

FactoryBot.define do
    factory :user do
        first_name { "John" }
        last_name { "Doe" }

        after(:create) do |user|
            create_association1
            create_association2
            create_association3
        end
    end
end
  • 巨大なデフォルトデータになっている
  • これを利用するすべてのテストケースで最小限の前提状態の生成になっていない
  • 無駄なデータ生成になっている
    • 間違った前提状態になる
    • パフォーマンス悪化につながる

最小限のデータセット定義をデフォルトFactoryとして定義するのがベストプラクティス。

spec/factories/~.rb

user = build(:user)
user = create(:user)
attrs = attributes_for(:user)
stub = build_stubbed(:user)

buildで済むのにcreateを使っている

build でいいところをあえて create にしてしまっている。

user = build(:user)
user = create(:user)
  • create をすると、DBに保存された状態を作り出す
  • build をすると、インスタンスを生成するだけで save はしない
  • 必ずしも create にする必要はない
  • バリデーションテストの場合、インスタンスさえできていればよい
  • SQLが実行されるぶん、パフォーマンスが悪化される
    • 塵も積もればテスト全体が遅くなる

ちなみに、これは「Everyday Rails」にも掲載されていたので自分も覚えていた。

なお、build だと created_at が生成されないので、これが作成日時が必要な場合はbuild_stubbedを使えばよい。

user = build_stubbed(:user)

生成したデータの配列を返す build_list, create_list

正しい方法

built_users = build_list(:users, 25)
created_users = create_list(:users, 25)

間違った方法

factory :user do
    factory :user_with_posts do
        after(:create) do |user|
            create_list(:post, 10, user: user)
        end
    end
end
  • 不必要に create_list して無駄なデータ生成をおこなっている
  • 本当に必要な数だけ指定してレコード生成するようにする

知らなかったこと

知らないことがたくさんあったため、後で調べるようにメモしておく。

attrs = attributes_for(:user)
stub = build_stubbed(:user)

FactoryBotの使い方ではないけど、新規ユーザーかどうかをチェックするメソッドも参考になった。(どんな期間で新規と定義するかはそのサービスの運営方針によるだろうけど)

class User < ActiveRecord::Base
    def new?
        create_at > 1.day.ago
    end
end

FactoryBotが提供するLintツールがある。

FactoryBot.lint

定義されたFactoryを一通りcreateし、invalidなFactory定義を検知する。rakeタスク化して他のRubocop等のLintツールと同様にCIタスクのひとつとして実装しておくとよい。

感想

  • 正しく定義ができている部分もあって少しだけ自信になった
  • traitをあまり使えてないことに気づいたので、まず復習しよう
  • FactoryBotの使い方で迷ったときにまた見たい
  • 自分が知らないことを知れるので、こういったカンファレンスを視聴するのは大事だと思った。自分の引き出しが増える(しかも無料で)