Fabrication hay FactoryGirl nhanh hơn khi viết Rspec
Lời nói đầu Uni-test là 1 phần hết sức quen thuộc trong các dự ánh Ruby on Rails. Có khá nhiều testing framework dành cho Ruby on Rails, nhưng quen thuộc và có cú pháp đơn giản nhất phải kể đến là Rspec. Vậy Rspec là gì? ... Thì ở phạm vi bài viết này mình sẽ không đề cập đến, để tìm hiểu thêm ...
Lời nói đầu
Uni-test là 1 phần hết sức quen thuộc trong các dự ánh Ruby on Rails. Có khá nhiều testing framework dành cho Ruby on Rails, nhưng quen thuộc và có cú pháp đơn giản nhất phải kể đến là Rspec. Vậy Rspec là gì? ... Thì ở phạm vi bài viết này mình sẽ không đề cập đến, để tìm hiểu thêm các bạn có thể tham khảo các bài viết về Rspec khác trên Viblo.
Dạo gần đây, Mainteance 1 dự án RoR trong quá trình viết Rspec, mình thấy trong Project đang phát triển có sử dụng gem Fabrication để tạo ra các đối tượng kiểm thử. Với mình thì gem này khá lạ, vì trước mình đều dùng Factory Girl hay mới đây là Factory Bot để tạo các đối tượng kiểm thử. Nên cũng thử tìm hiểu xem Fabrication nó như thế nào, và cụ thể là so sánh với Factory Girl thì cái nào sẽ tạo dữ liệu thử nghiệm nhanh hơn?
Ở bài viết này mình cũng chỉ tập trung so sánh tốc độ tạo dữ liệu thử nghiệm của 2 Gem, chứ không đi sau vào phân tích hay tìm hiểu cách sử dụng 1 gem nào đó.
Setup
Để kiểm tra hiệu suất của 2 gem này trong việc tạo dữ liệu thử nghiệm? Chúng ta tạo ra 1 hệ thống nho nhỏ có chứa 1 vài model với các quan hệ như dưới đây:
rails generate model address street:string city:string state:string postal:string
class CreateAddresses < ActiveRecord::Migration[5.1] def change create_table :addresses do |t| t.string :street, null: false t.string :city, null: false t.string :state, null: false t.string :country, null: false t.string :postal, null: false t.timestamps end end end
class Address < ApplicationRecord has_one :company validates :street, presence: true validates :city, presence: true validates :state, presence: true validates :country, presence: true validates :postal, presence: true end
rails generate model company name:string address:references
class CreateCompanies < ActiveRecord::Migration[5.1] def change create_table :companies do |t| t.string :name, null: false, index: { unique: true } t.references :address, null: false, index: true t.timestamps end add_foreign_key :companies, :addresses end end
class Company < ApplicationRecord belongs_to :address has_many :employees validates :name, presence: true, uniqueness: true end
rails generate model employee name:string email:string company:references
class CreateEmployees < ActiveRecord::Migration[5.1] def change create_table :employees do |t| t.string :name, null: false t.string :email, null: false, index: { unique: true } t.references :company, null: false t.timestamps end add_foreign_key :employees, :companies end end
class Employee < ApplicationRecord validates :name, presence: true validates :email, presence: true, uniqueness: true belongs_to :company end
Sau khi tạo 1 hệ thống để test như trên, chúng ta sẽ tạo ra các factories và fabricators giống hệt nhau như sau:
Fabricator(:address) do street '123 Sandhill Road' city 'Whitehorse' state 'Yukon' country 'Canada' postal '00000' end FactoryGirl.define do factory :address do street '123 Sandhill Road' city 'Whitehorse' state 'Yukon' country 'Canada' postal '00000' end end Fabricator(:company) do name { sequence { |index| "Company ##{index}" } } address end FactoryGirl.define do factory :company do sequence(:name) { |index| "Company ##{index}" } association :address, strategy: :build end end Fabricator(:employee) do name { sequence { |index| "Employee ##{index}" } } email { sequence { |index| "#{index}@fake.host" } } company end FactoryGirl.define do factory :employee do sequence(:name) { |index| "Employee ##{index}" } sequence(:email) { |index| "#{index}@fake.host" } association :company, strategy: :build end end
Điểm Benchmarks
Điểm Benchmarks để so sánh sự khác biệt giữa Fabricator và Factory Girl (cụ thể là chỉ số bmbm) sẽ được tạo ra thông qua việc create và build các đối tượng thử nghiệm. Các điểm chuẩn chạy trong 1 transaction có thể kết nối với nhau (tương tự như cách chúng được gọi trong môi trường thử nghiệm)
require 'benchmark' ITERATIONS = 50_000 def autorollback ApplicationRecord.transaction(joinable: true) do yield raise ActiveRecord::Rollback end end Benchmark.bmbm(32) do |benchmark| [Fabricate, FactoryGirl].each do |service| %i[address company employee].each do |resource| benchmark.report("#{service}.build(#{resource.inspect})") do ITERATIONS.times { autorollback { service.build(resource) } } end benchmark.report("#{service}.create(#{resource.inspect})") do ITERATIONS.times { autorollback { service.create(resource) } } end end end end
Kết quả
Các kết quả sau đây được tính bằng giây
user system total real Fabricate.build(:address) 25.620000 2.340000 27.960000 ( 30.918019) Fabricate.create(:address) 62.510000 6.780000 69.290000 ( 79.881861) Fabricate.build(:company) 31.290000 2.230000 33.520000 ( 36.253026) Fabricate.create(:company) 134.810000 12.270000 147.080000 (173.582866) Fabricate.build(:employee) 40.980000 2.300000 43.280000 ( 46.014712) Fabricate.create(:employee) 220.580000 19.250000 239.830000 (286.526900) FactoryGirl.build(:address) 24.420000 2.070000 26.490000 ( 29.069839) FactoryGirl.create(:address) 61.420000 6.440000 67.860000 ( 77.759625) FactoryGirl.build(:company) 34.430000 2.210000 36.640000 ( 39.499374) FactoryGirl.create(:company) 173.610000 14.070000 187.680000 (221.798007) FactoryGirl.build(:employee) 45.290000 2.290000 47.580000 ( 50.445987) FactoryGirl.create(:employee) 256.920000 20.540000 277.460000 (330.741249)
Tổng kết
Dựa vào các kết quả thử nghiệm trên thì có thể thấy Fabricate có 1 lợi thế nhỏ so với Factory Girl trong việc build và create các resources nested.
Sự khác biệt kể trên là ít hơn mười phần trăm (có thể chấp nhận được nếu bỏ qua hiệu suất).
Như vậy, chúng ta hãy lựa chọn Gem mà chúng ta cảm thấy thuận tiện hơn trong việc viết Rspec vì cả 2 gem này đều có tốc độ tạo dữ liệu thử nghiệm là ngang bằng nhau trong các trường hợp bình thường và 1 chút xíu khác biệt trong các trường hợp phức tạp hơn