はじめに
こんにちは、エンジニア3年目の嶋田です。
この記事を開いていただきありがとうございます!
私がエンジニアとして働き始めてから、テストの重要性を痛感しています。
特にRspecを使用したテストは、コードの品質を保つために欠かせません。
そして、テストデータの生成にはFactory Botが非常に役立ちます。
今回は、Rspecで使用するFactory Botの基本的な使い方や便利な機能についてまとめてみました。
この記事が皆さんのテストコードの質を向上させる一助となれば幸いです。
より詳しく知りたい方は、以下のページを参照して下さい。
(※この記事の内容の大半は、以下のページに書かれている内容です)
Factory Bot Getting Started
目次
Factory Botとは
Factory BotはRspec標準で使える"fixture"に代わり、
テストデータの準備をサポートしてくれるライブラリです。
"factory_bot_rails"はRails向けの拡張版のFactorybot
主にRspecと組み合わせて使用され、テストデータを柔軟かつ効率的に作成することができます。
主な特徴
- 簡潔なシンタックスでテストデータを定義
- 依存関係を考慮したデータ生成
- traitやコールバックを使った柔軟なデータ生成
Factoryの定義
まずは、Factoryの基本的な定義方法について説明します。
以下の例では、User
モデルのFactoryを定義します。
# spec/factories/users.rb
FactoryBot.define do
factory :user do
name { "John Doe" }
email { "john.doe@example.com" }
password { "password" }
end
end
-
FactoryBot.define do
ブロックの中に、factory :user do
と記述することでUser
モデルのFactoryを定義します。 - 各属性はブロック内で指定し、具体的な値を設定します。
- この場合、
name
、email
、password
がそれぞれ設定されています。
- この場合、
基本的なファクトリー定義方法
必要最低限の属性を持ったファクトリーを定義する
インスタンス化するのに必要最低限の属性を持ったファクトリーを各クラスで一つ定義します。
具体的には、オブジェクト作成時にバリデーションを通すために必要で、且つデフォルト値を持っていない属性を定義します。
# spec/factories/users.rb
FactoryBot.define do
factory :user do
sequence(:email) { |n| "test#{n}@example.com" } # シーケンスを使う
first_name { "John" }
last_name { "Doe" }
date_of_birth { 18.years.ago } # 値を動的に生成する
organization # Organizationへのアソシエーション
end
end
ハッシュを扱う場合
JSONカラムなどを扱う場合は、以下のように{{ key: value }}とする必要があります。
# spec/factories/programs.rb
FactoryBot.define do
factory :program do
configuration { { auto_resolve: false, auto_define: true } }
end
end
Factoryの使い方
次に、定義したFactoryを使用してデータを生成する方法を紹介します。
以下の例は、Rspecのテスト内でFactory Botを使用する方法です。
# spec/models/user_spec.rb
require 'rails_helper'
RSpec.describe User, type: :model do
it "is valid with valid attributes" do
user = FactoryBot.build(:user)
expect(user).to be_valid
end
it "has a unique email" do
user1 = FactoryBot.create(:user, email: "unique@example.com")
user2 = FactoryBot.build(:user, email: "unique@example.com")
expect(user2).not_to be_valid
end
end
-
FactoryBot.build(:user)
で、データベースに保存されていないUser
オブジェクトを生成します。 -
FactoryBot.create(:user)
で、データベースに保存されたUser
オブジェクトを生成します。- 必要に応じて属性をオーバーライドすることもできます。
buildとcreateの使い分け
-
build
:メモリ上にだけインスタンスを生成します。データベースへの影響はありません。 -
create
:インスタンスを生成し、データベースに保存します。これにより、データベースの状態を変更するテストが可能になります。
# インスタンスを生成して返す。インスタンスはまだDBに保存されていない。
user = build(:user)
# DBに保存されたインスタンスを返す。
user = create(:user)
build_listとcreate_list
build_list
、create_list
を使うと複数のインスタンスを生成できます。
users = build_list(:user, 2)
users = create_list(:user, 2)
属性を指定する
属性を指定することで、特定の条件に合ったインスタンスを生成できます。
# factoryとして定義されていなくても、テーブルに存在するカラムを指定可能
user = create(:user, first_name: "Dave", role: "admin")
traitの使用
traitを使用すると、Factoryに特定の状態や属性を追加することができます。以下に、traitを使用した例を示します。
# spec/factories/users.rb
FactoryBot.define do
factory :user do
name { "John Doe" }
email { "john.doe@example.com" }
password { "password" }
trait :admin do
admin { true }
end
trait :age_20 do
age { 20 }
end
trait :age_19 do
age { 19 }
end
trait :like_vegetables do
like { "野菜" }
end
trait :like_fish do
like { "魚" }
end
end
end
-
trait :admin do
ブロック内で、admin
属性をtrue
に設定します。 -
age
やlike
の属性もtraitとして定義することで、組み合わせの自由度が高くなります。
traitのメリット
traitの大きなメリットは、組み合わせの自由度の高さです。必要なテストデータを小さく分けて定義し、必要に応じて組み合わせることで、様々なテストシナリオに対応できます。
例えば、以下のように組み合わせて使用します。
it { expect(build(:user, :age_20, :like_vegetables).method_X).to eq("パターン1") }
it { expect(build(:user, :age_20, :like_fish).method_X).to eq("パターン2") }
it { expect(build(:user, :age_19, :like_vegetables).method_X).to eq("パターン3") }
it { expect(build(:user, :age_19, :like_fish).method_X).to eq("パターン4") }
このようにすることで、テストデータの意図が明確になり、必要なデータを簡単に組み合わせることができます。
具体例
例えば、20歳が閾値のテストがある場合、以下のようにtraitを利用してテストデータを作成します。
factory :user do
name { "taro" }
trait :age_20 do
age { 20 }
end
trait :age_19 do
age { 19 }
end
end
この方法で、テストの軸が増えても対応しやすくなります。例えば、20歳以上と未満、好きな食べ物が野菜かそれ以外かの組み合わせで4パターンのテストデータを作成する場合も簡単です。
it { expect(build(:user, :age_20, :like_vegetables).method_X).to eq("パターン1") }
it { expect(build(:user, :age_20, :like_fish).method_X).to eq("パターン2") }
it { expect(build(:user, :age_19, :like_vegetables).method_X).to eq("パターン3") }
it { expect(build(:user, :age_19, :like_fish).method_X).to eq("パターン4") }
すっきりと意図が明確なテストデータが用意できるようになります。テスト作成者が自由に必要なデータを組み合わせることができます。
もちろん例のような簡単なケースなら、以下のように事前定義不要の可能性もあります。
build(:user, age: 20, like: "野菜")
しかし、汎用性がありそう、再利用される、初期化が手間、などの場合には、traitでテストデータを用意することも有益です。
コールバックの使用
コールバックを使用すると、オブジェクトの生成前後に特定の処理を実行できます。以下に、コールバックを使用した例を示します。
# spec/factories/users.rb
FactoryBot.define do
factory :user do
name { "John Doe" }
email { "john.doe@example.com" }
password { "password" }
after(:create) do |user|
create_list(:post, 3, user: user)
end
end
end
callbackとtransientを組み合わせる例
callbackをtransientと組み合わせて使うことで、transientが指定された時に関連付けられたデータを生成します。
# spec/factories/users.rb
FactoryBot.define do
factory :user do
# その他の属性
transient do
posts_count { 0 }
end
after(:create) do |user, evaluator|
create_list(:post, evaluator.posts_count, user: user) if evaluator.posts_count.positive?
end
end
end
callbackとtraitを組み合わせる例
callbackをtraitと組み合わせて使うことで、traitが指定された時に関連付けられたデータを生成します。
# spec/factories/users.rb
FactoryBot.define do
factory :user do
# その他の属性
trait :user_with_departments do
after(:create) do |user, _|
create(:department, :sales, user: user)
end
end
end
end
使用例
pattern 1 を使う場合
userに紐付いたpostsが、after callback(after(:create)
)によって生成されます。
create(:user, posts_count: 1)
pattern 2 を使う場合
create(:user, :user_with_departments)
Fakerの使用
Fakerを使うと、ランダムなダミーデータを簡単に生成できます。これにより、テストデータに多様性を持たせることができます。
Fakerのインストール
Gemfileに以下の行を追加し、bundle install
を実行します。
gem 'faker'
Fakerの使用例
以下は、Fakerを使用してユーザーデータを生成する例です。
# spec/factories/users.rb
FactoryBot.define do
factory :user do
name { Faker::Name.name }
email { Faker::Internet.email }
password { Faker::Internet.password(min_length: 8) }
end
end
-
Faker::Name.name
でランダムな名前を生成します。 -
Faker::Internet.email
でランダムなメールアドレスを生成します。 -
Faker::Internet.password(min_length: 8)
でランダムなパスワードを生成します。
まとめ
以下に、今回説明したFactory Botの主な特徴と使用シナリオをまとめました。
機能 | 説明 | 使用シナリオ |
---|---|---|
Factoryの定義 | モデルごとにデフォルトの属性を持つテストデータを定義。 | 基本的なテストデータの生成。 |
traitの使用 | 特定の状態や属性を持つデータを追加で定義。 | 特定の条件下でのテストデータの生成。 |
コールバックの使用 | オブジェクトの生成前後に特定の処理を実行。 | 関連データの生成や追加のセットアップが必要な場合。 |
Associationの使用 | 関連するオブジェクトを自動的に生成。 | モデル間の関係性を考慮したテストデータの生成。 |
Fakerの使用 | ランダムなダミーデータを生成。 | テストデータに多様性を持たせたい場合。 |
Factory Botを使うことで、テストデータの生成が容易になり、テストコードの可読性とメンテナンス性が向上します。皆さんもぜひ、これらのテクニックを活用して、効率的なテストライティングを実現してください。
読んでいただき、ありがとうございました!