読者です 読者をやめる 読者になる 読者になる

woshidan's blog

そんなことよりコードにダイブ。

FactoryGirlとfixturesでテストにかかる時間を比べてみた

FactoryGirl fixtures Ruby

7 Reasons I'm Sticking With Minitest and Fixtures in Rails | Brandon Hilkert

この辺の記事にFactoryGirlよりfixturesの方が早くない? と書いてあったので、ちょっと単純なテストを200個くらい用意して、 どれくらい速くなるものかテストしてみました。

使用したのはその辺のMBA(8GB)です。

テストの実行ライブラリはMinitestの方です。

内容

  • テストの内容
  • 共通の準備
  • fixtures側の準備
  • FactoryGirl側の準備
  • fixtures側の実行結果
  • FactoryGIrl側の実行結果
  • まとめ

テストの内容

本当にFactoryGirlで動的にデータベースにレコード用意するのがfixturesで最初からデータ置いている場合と比べて実際どの程度時間がかかるか見たいだけだったので、若干、テストコードに差はありますが、

  • レコードの数を数える
  • 単純にアソシエーションを使って、もう一方のレコードを呼び出す

みたいな感じです。

# fixturesを使った場合
# setupメソッドは不要

# これを下に200個くらい並べました
  test "time trial 01" do
    assert_equal 2, User.count
    assert_equal items(:one), users(:one).items.first
  end
# FactoryGirlを使った場合
  def setup
    @user_one = create(:user_one)
    create(:user_two)
    @item_one = create(:item_one, user: @user_one)
    create(:item_two, user: @user_one)
    create(:item_three, user: @user_two)
  end

# これを下に200個くらい並べました
  test "time trial 01" do
    assert_equal 2, User.count
    assert_equal @item_one, @user_one.items.first
  end

共通の準備

あと出しになっていますが、以下のようにUserモデルと、Userモデルと1対多の形でItemモデルを用意。
ある程度列が多いテーブルにしたかったのでUserモデル無駄に住所とか持ってます。

# app/models/user.rb
class User < ActiveRecord::Base
  # t.string   "first_name"
  # t.string   "last_name"
  # t.integer  "age"
  # t.string   "postal_code"
  # t.string   "address_1"
  # t.string   "address_2"
  # t.string   "address_3"
  # t.datetime "created_at"
  # t.datetime "updated_at"
  has_many :items
end
# app/models/item.rb
class Item < ActiveRecord::Base
  # t.string   "name"
  # t.integer  "user_id"
  # t.text     "memo"
  # t.integer  "category_id"
  # t.datetime "created_at"
  # t.datetime "updated_at"
  belongs_to :user
end

fixtures側の準備

# test/test_helper.rb
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'

class ActiveSupport::TestCase
  # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
  fixtures :all

  # Add more helper methods to be used by all tests here...
end
test/fixtures/users.yml
one:
  id: 1
  first_name: 太朗
  last_name: 田中
  age: 20
  postal_code: 111-1111
  address_1: 東京都どこか区
  address_2: そのへん町1-2-3
  address_3: アパート適当456
two:
  id: 2
  first_name: 次郎
  last_name: 佐藤
  age: 30
  postal_code: 222-2222
  address_1: 何とか県いずこ市
  address_2: あのへん町3-2-1
  address_3:
test/fixtures/items.yml
one:
  id: 1
  name: first
  user_id: 1
  memo: memo
  category_id: 1
two:
  id: 2
  name: second
  user_id: 1
  memo: memomemo
  category_id: 1
three:
  id: 3
  name: third
  user_id: 2
  memo: memomemomemo
  category_id: 1

FactoryGirl側の準備

# test/test_helper.rb 
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'

class ActiveSupport::TestCase
  include FactoryGirl::Syntax::Methods
  # Add more helper methods to be used by all tests here...
end
# factories/users.rb
FactoryGirl.define do
  factory :user_one, class: User do
    first_name '太朗'
    last_name '田中'
    age 20
    postal_code '111-1111'
    address_1 '東京都どこか区'
    address_2 'そのへん町1-2-3'
    address_3 'アパート適当456'
  end

  factory :user_two, class: User do
    first_name '次郎'
    last_name '佐藤'
    age 30
    postal_code '222-2222'
    address_1 '何とか県いずこ市'
    address_2 'あのへん町3-2-1'
    address_3 ''
  end
end
# factories/items.rb
FactoryGirl.define do
  factory :item_one, class: Item do
    name 'first'
    memo 'memo'
    category_id 1
  end

  factory :item_two, class: Item do
    name 'second'
    memo 'memomemo'
    category_id 1
  end

  factory :item_three, class: Item do
    name 'third'
    memo 'memomemomemo'
    category_id 1
  end
end

fixtures側の実行結果

Finished in 0.511908s, 390.6953 runs/s, 781.3906 assertions/s.
Finished in 0.471763s, 423.9416 runs/s, 847.8831 assertions/s.
Finished in 0.509364s, 392.6468 runs/s, 785.2937 assertions/s.
Finished in 0.492756s, 405.8800 runs/s, 811.7601 assertions/s.
Finished in 0.509669s, 392.4114 runs/s, 784.8228 assertions/s.

FactoryGIrl側の実行結果

Finished in 2.042317s, 97.9280 runs/s, 195.8560 assertions/s.
Finished in 1.973356s, 101.3502 runs/s, 202.7004 assertions/s.
Finished in 2.026818s, 98.6768 runs/s, 197.3537 assertions/s.
Finished in 1.966871s, 101.6844 runs/s, 203.3687 assertions/s.
Finished in 1.949037s, 102.6148 runs/s, 205.2296 assertions/s.

まとめ

レコードの作成に対してFactoryGirlを使用してその都度作成するより、
fixturesを使用した方が、その都度レコードを作成/削除しなくてよかったり
テスト用にインスタンス変数を持たなくてよかったりするので、
1.5/200 ( s/test ) 速くなるというお話でした。

個人的な感想としては、最初から書くなら時間というより思っていたよりAssociationが書きやすかったためfixturesに寄せようかなと思います。

既にFactoryGirlでかなりの量のテストを書いている場合、
全部置き換えたらそれなりに速くなるかもしれないですが、
安全に簡単に置き換えられるのは、最初のいつものuserなどのレコード作成くらいな気がしていて、
そうすると100件あたり1s以下くらい? という話になるので、焼け石に水な印象でした。