Journey

技術に関することと覚書と

FactoryBotを効果的に使用する

似た記事をQiitaに書いてますが、こっちをメインにする意味でも書き直そうと思います。

はじめに

ご存じの方も多いと思いますが、FactoryBotは主にテストを書く際に使用される、オブジェクトのインスタンス化を簡単にするためのものです。 ただ、FactoryBotは柔軟にオブジェクトを生成できてしまうため、よくメンテが辛かったり、何をテストしているのかがわからない状況になってしまうことが多くあるかと思います。 そうならないためのTipsをまとめようと思います。

外部キーを直指定しない

よくない例

FactoryBot.define do
  factory :post do
    title { 'FactoryBotを効果的に使用する' }
    user_id { 1 }
  end
end

factoryの定義上で、 user_id1 というマジックナンバーで定義してしまっています。 こうしてしまうと、このfactoryは id1 のユーザーがいることが前提のfactoryになってしまい、テストをする際に id1 のユーザーがいることを気にしないとダメなのはちょっと微妙ですね。 なので基本的にはマジックナンバーで指定するのではなく、別factoryを指定する方法があるのでそちらを使用しましょう。

改善例

FactoryBot.define do
  factory :user do
    name { 'ippachi' }
  end

  factory :post do
    title { 'FactoryBotを効果的に使用する' }
    user
  end
end

このように書くと、factory :user の定義を使用し、そのユーザーと紐付けてくれます。 参考: https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md#associations

factoryに定義している値をテストで直接使用しない

よくない例

Class User
  scope :adult, lambda do
    where('users.age >= ?', 20)
  end
end

FactoryBot.define do
  factory :user do
    title { 'FactoryBotを効果的に使用する' }
    age { 20 }
  end
end

describe 'adult scope' do
  let(:user) { create(:user) }

  it 'returns a user' do
    expect(User.adult).to eq [user]
  end
end

例では User モデルの adult というスコープのテストを書いています。 その中で、 age が20以上であるユーザーを絞り込んでいるのですが、ユーザーの age はfactoryで定義されています。 これはあまり良くありません。 例えば、このサービスのメインのユーザーが18歳であることがあとから判明した場合にfactoryの定義を18に変えたほうが、今後追加されるテストが書きやすくなることは想像できます。 ただそれをするとこのテストは当然ですが落ちます。 なので、テストに使う値は、同じものを上書きする形になったとしても、しっかりと指定するようにしましょう。

改善例

describe 'adult scope' do
  let(:user) { create(:user, age: 20) }

  it 'returns a user' do
    expect(User.adult).to eq [user]
  end
end

複数回生成してもエラーにならないようにする

よくない例

FactoryBot.define do
  factory :user do
    email { 'test@example.com' } # unique
  end
end

describe 'example' do
  it 'test' do
    create(:user)
    create(:user)
  end
end

例ではユーザーのメールアドレスを定義していますが、これはユニークですので複数回生成すると、DBエラーが吐かれます。 単にユーザーが複数欲しいとき、例えば、ユーザーのスコープのテストのときにそれぞれ指定するのは面倒です。 そういうときは sequence を使用しましょう。sequence は複数回factoryを作成した際にそのデータにインデックスを付けたいときなどに使用します。

改善例

FactoryBot.define do
  factory :user do
    sequence(:email) { |i| "test#{i}@example.com" }
  end
end

describe 'example' do
  it 'test' do
    create(:user)
    create(:user)
  end
end

参考: https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md#sequences

FactoryBotをモデルのUnitテストではあまり使用しないようにする

例えばモデルのUnitテストはrequestテストやE2Eテストと違い、そのモデルに注目したテストです。 そのテスト内でテスト対象のオブジェクトがどのように生成されたのかも大事な要素になります。 なのでfactoryを用いて生成せずに、できるだけ new を使用して生成するようにしましょう。

他にもtraitを使うとかassociationなども色々ある気がしますが使い方を誤ると余計にメンテしづらくなるので、とりあえずこのくらいにしておきます。