意味のあるテストデータの作成

(EverydayRailsRSpecによるRailsテスト入門第4章)

はじめに

このテーマにした理由

 

・flateaでテストを読み書きする中で、FactoryBotを使ったデータの生成を理解するのに苦戦しました。

 

 

 

はじめに

このテーマにした理由


・flateaでテストを読み書きする中で、FactoryBotを使ったデータの生成を理解するのに苦戦しました。


→ これを機に基礎を理解したいと思い、

  EverydayRailsRSpecによるRailsテスト入門第4章

   「意味あるデータの作成」についてまとめることにしました。


概要

①ファクトリ対フィクスチャ

②FactoryBotをインストールする

③アプリケーションにファクトリを追加する

④シーケンスを使ってユニークなデータを生成する

⑤ファクトリで関連を扱う

⑥ファクトリ内の重複をなくす

⑦コールバック

⑧ファクトリを安全に使うには

⑨まとめ 

①ファクトリ対フィクスチャ

フィクスチャとは??

 

 

Railsにもともと備わっている、データを生成してくれる機能です。

 

フィクスチャのメリット

 

 

・フィクスチャは比較的速い

・Railsに最初から付いてくる

フィクスチャのデメリット

 

 

 

・テストとは別のフィクスチャファイルに保存された値を覚えておく必要がある

・フィクスチャはもろくて壊れやすい(らしい)

・Railsはフィクスチャのデータを作るときActive Recordを読み込まない

ファクトリの方がオススメな理由

 

 

・ファクトリはシンプルで柔軟性に富んだテストデータ構築できる

 

・ファクトリは適切に使えば、テストをきれいで読みやすく、リアルなものに保ってくれる

 

 

しかし、多用しすぎると遅いテストの原因になることもある(らしい)

②FactoryBotをインストールする

group :development, :test do ~ end の中にgem 'factory_bot_rails'を追加し、bundle installします

 

 

 

group :development, :test do
 略
  gem 'factory_bot_rails'
 略
end

③アプリケーションにファクトリを

追加する

spec/factorys/users.rbに属性や値を書いていきます。

 

 

 

 

 

 

このような設定をしておくだけで、テスト内で FactoryBot.create(:user) と書くと、定義したuserのデータが生成されます。

 

 

FactoryBot.define do
  factory :user do
    name 'hayashi'
    email "test@gmail.com" 
    password '11111111'   
  end
end

このような設定をしておくだけで、テスト内で FactoryBot.create(:user) と書くと、定義したuserのデータが生成されます。

require 'rails_helper'

  describe 'User' do
    it '有効なファクトリを持つ事' do
    expect(FactoryBot.create(:user)).to be_valid
    end
  end
end

spec使わずに書くとき

一方、FactoryBotを使用せず書くと、以下のようになります。

 

 

 

 

 

require 'rails_helper'

  describe 'User' do
    it '有効なファクトリを持つ事' do
    user = User.create(
     name 'hayashi',
     email "test@gmail.com", 
     password '11111111'       
    )
    actexpect(:user).to be_valid
    end
  end
end

FactoryBotを使うと簡潔に書くことができる一方、どんな値を持ったデータが生成されているのかテストファイルを直接みるだけではわからないという注意点もあります。

 

時と場合によって、FactoryBotを使わずデータをベタ書きするのか、FactoryBotを使うのか使い分けるのが良いようです。

④シーケンスを使って

ユニークなデータを生成する

先ほど作ったファクトリを何回実行しても同じ値を持ったデータが作成されます。

メールアドレスのように、ユニークなデータでないとバリデーションエラーを起こしてしまう物に関しては、シーケンスを使ってユニークなデータを生成してあげることができます。

 

 

 

FactoryBot.define do
  factory :user do
    name 'hayashi'
    sequence(:email) { |n| "test#{n}@gmail.com"  } 
    password '11111111'   
  end
end

⑤ファクトリで関連を扱う

FactoryBotはアソシエーションを扱うときにとても便利です。

 

(例)

userモデル、projectモデル、noteモデルがあり、

・userモデルがhas_many :notes、has_many :projectsで、

・projectモデルがbelongs_to :user、has_many :notes

・noteモデルがbelongs_to :user、belongs_to :project

であるデータを作成する際は次のように作成できます。

 

 

FactoryBot.define do
  factory :note do
    message 'I am hayashi'
    association :project 
    user {project.owner}  // assosiation :user としてしまうと
  end                                   userが重複して作られてしまう
end
 


FactoryBot.define do
  factory :project do
    sequence(:name) { |n| "project#{n}" }
    association :owner
  end
end


FactoryBot.define do
  factory :user, aliases: [:owner] do
    name 'hayashi'
    sequence(:email) { |n| "test#{n}@gmail.com"  } 
    password '11111111'   
  end
end

あとは、以下のように、FactoryBot.create(:note) とnoteを作成すればOKです。

 

 

require 'rails_helper'

  describe 'User' do
    it '有効なファクトリを持つ事' do
    expect(FactoryBot.create(:note)).to be_valid
    end
  end
end

⑥ファクトリ内の重複をなくす

ファクトリで、ある属性の値だけ別なファクトリを作りたい時は以下のように設定します。

 

 

 

 

 

 

 

こうすれば、

FactoryBot.create(:project)

FactoryBot.create(:project_due_yesterday)

とスペックでファクトリを呼び分けることで違うデータを持ったファクトリを生成することができます。

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

  factory :project_due_yesterday, class: Project do
    sequence(:name) { |n| "project#{n}" }
    due_on 1.day.from_now 
    association :owner
  end
end

しかし、これだと、nameやassosiationの部分が重複しています。

この重複を無くした書き方も以下のようにすることで可能です。

 

 

 

 

 

 

 

 

 

factory :project の中に factory :project_due_yesterday を入れ子にすることで、重複していたnameやassosiationの記述、class: Projectの記述を省略できます。

FactoryBot.define do
  factory :project do
    sequence(:name) { |n| "project#{n}" }
    due_on 1.week.from_now 
    association :owner
  
    factory :project_due_yesterday do    // ネストさせた
      due_on 1.day.from_now          
    end
  end
end

また、ファクトリをネストさせる他にも、traitを使った書き方もあります。

 

 

 

 

 

 

 

 

traitを用いて書いた時、スペックでファクトリを生成する際は、

FactoryBot.create(:project, :due_yesterday)

と引数でtraitを指定します。

FactoryBot.define do
  factory :project do
    sequence(:name) { |n| "project#{n}" }
    due_on 1.week.from_now 
    association :owner
  
    trait :due_yesterday do
      due_on 1.day.from_now          
    end
  end
end

⑦コールバック

あるモデルをcreate・newした後、別のモデルも作成したい場合はコールバックを使用します。

コールバックをtraitを用いて書けば、簡単にコールバックを呼んだり呼ばなかったり選択することができます。

 

 

 

 

 

 

 

こうしておくと、FactoryBot.create(:project, with_note) とすればコールバックが実施され、projectの関連先のnoteのデータも作成してくれます。

FactoryBot.create(:project) と通常通りならコールバックは実施されません。

 

これ以外にも、traitを使えば非常に複雑なデータの作成もスッキリかけるようになります。

 

FactoryBot.define do
  factory :project do
    sequence(:name) { |n| "project#{n}" }
    due_on 1.week.from_now 
    association :owner
  
    trait :with_note do
      after(:create){|project| create_list(:note, 5, project: project)}       
    end
  end
end

⑧ファクトリを安全に使うには

ファクトリを使うとテスト中に予期しないデータが作成されたり、無駄にテストが遅くなったりする原因になります。

 

(コールバックを使って関連するデータを作成する必要があるなら、ファクトリを使うたびに呼び出されないよう、トレイトの中でセットアップするようにするなど気をつけます。)

 

⑨まとめ

・ファクトリを使うことで、複雑なデータでもスペック内でスッキリと呼び出すことが可能になります。

 

・テストは理解しやすいものであることが大切なので、初心者の場合、ファクトリではなくnewやcreateでベタ書きするのもありだよ、と魚さんも言っていました。 

 

・ファクトリの便利さを知れたので、まずは使い方をもっと理解し、パフォーマンスや可読性についても考慮してかけるようになりたいです。

 

ご静聴

ありがとうございました

deck

By hayashiyoshino

deck

  • 220