規劃 FactoryBot 小技巧,你的測試可以做得更好
FactoryBot(前身為 FactoryGirl)為知名軟體方案解決公司 ThoughtBot 所開發,其用途非常地單純,就是讓使用者可以輕鬆地生產測試用資料。
而使用 FactoryBot 的方式也相當簡單。只要安裝了 Gem 並新增一個 Factory,就可以產生資料。
Factory 長這個樣子
FactoryBot.define do
factory :user do
first_name "John"
last_name "Doe"
admin false
end
end
然而雖然 FactoryBot 相當好入門,但是當你在規劃 Factories 的時候,有一些細節必須要注意,否則在未來程式變得複雜化的同時,在測試上恐怕會衍生出許多不必要的麻煩。
避免指定非必要欄位
非必要欄位有可能是自動生成的欄位(有 Default 值,例如 created_at),或是不影響資料庫保存的欄位(不需要驗證、可以為 Null 的欄位),倘若這些資料在測試中佔有重要的一環,則使用 trait 來指定。
舉例來說,假設你有個測試要找出加入超過兩年以上的 User,那麼可以寫成這個樣子:
FactoryBot.define do factory :user do first_name "John" last_name "doe" trait :old do joined_at 2.years.ago end end end
最忌諱的就是在限制 Unique 的欄位直接賦值,千萬不要這麼做!因為當你未來要在一個 Test Case 裡面生成兩筆以上的資料的話,就會產生驗證錯誤而無法儲存資料。
# 請不要這麼做 FactoryBot.define do factory :user do first_name "John" last_name "doe" id_number 'E1222333444' #unique column end end
指定有限制欄位有方法
倘若我們真的有需要在 Unique 的欄位賦值,有什麼好方法嗎?你會有以下幾種做法。
- 手動賦值 如果只是偶爾需要賦值的情況下,我會建議直接在生產資料的時候手動賦值,例如
FactoryBot.create(:user, id_number: 'E1222333444')
,如此一來比較容易掌握生成的資料。 利用 Sequences 這是 FactoryBot 所提供的一個方法,可以以 auto incremental 的方式產生資料。
FactoryBot.define do factory :user do first_name "John" last_name "doe" # 第一個參數是欄位名稱,第二個參數則是起始的數值。 sequence(:id_number, '01') { |n| "E12223334#{n}" } end end
如此一來,每當用 FactoryBot 產生 User 的時候,他就會自動生成不重複的
id_number
,會從E1222333401
,E1222333402
這樣的形式來新增。不過這樣的做法會有一個問題,假設
id_number
有限制其字元數必定為 11,那麼當我們生產到第一百個 user 的時候,它的數值就會變成E12223334100
,會多出一個字元。為了避免這樣的情況,我們可以使用 Ruby 的#cycle
method,將它改成這樣子:sequence(:id_number, ('01'..'99').cycle) { |n| "E12223334#{n}" }
。 如此一來,當生產到第一百個 User 的時候,它就會自動迴圈變回 E12223333401 了。- 手動賦值 如果只是偶爾需要賦值的情況下,我會建議直接在生產資料的時候手動賦值,例如
善用 Trait 讓你的資料更淺顯易懂 上面有提到利用 trait 來指定非必要欄位,你也可以用 Trait 來讓你的程式碼可讀性更高。舉例來說,我們有一個欄位叫做
status
用來記錄會員的帳號狀態,那麼在定義 Factory 的時候便可以利用 trait 來標示這些狀態。 “`ruby FactoryBot.define do factory :user do firstname "John” lastname “doe” endtrait :active { status ‘active’ } trait :trial { status ‘trial’ } trait :retired { status ‘retired’ } trait :suspended { status ‘suspended’ } end “`
這樣的做法不僅讓你的 Factory 變得乾淨,同時當你在生成資料的時候,也可以一目瞭然。特別是當你的測試非常需要各種不同的狀態下變動時,能夠更清楚明白測試的目標和內容。
搭配 Faker 隨機生產資料 Faker 是對於命名能力缺乏的工程師的一種救贖。它的能力作用就是生產隨機資料,從一般的信用卡卡號和人物的人名,到權力遊戲和 Pokemon 的角色,它都可以幫你生出來,用來瘩配 FactoryBot 生產測試資料,可謂妙不可言。 不過,當使用 Faker 搭配 FactoryBot 的時候,有些事情必須要注意。首先是在寫 Factory 的時候,我們必須用 block 來賦值給欄位,假設我們用參數的方式賦值的話,他只會隨機產生出其中一個結果,意味著如果你的欄位是 unique 的話,產生第二筆以上資料就會發生錯誤。 ”`ruby FactoryBot.define do factory :user do firstname “John” lastname “doe” # 會重複出現一樣的公司名稱 company_name Faker::Company.name end end
FactoryBot.define do factory :user do firstname “John” lastname “doe” # 無論產生幾筆資料都是隨機的 company_name { Faker::Company.name } end end “`
另外一點則是,有些 Faker 內的 Seed 是有限的,遇到欄位是 Unique 的情況下必須謹慎使用 Faker,否則在建立愈多資料的情況,愈有可能隨機到相同的值造成資料庫的寫入錯誤。因為這種問題有隨機性,也比較難除錯,因此要盡可能避免這樣的狀況,當你發現某個測試常常出現這樣的情況,那就乾脆地手動賦值吧!
## 小結
FactoryBot 是個功能單純而且容易操作的套件,不過針對愈複雜的測試情況和應用也有著相當進階的功能,在這篇文章中提出一些初始規劃 Factory 時應該避免或者注意的地方,希望拋磚引玉能讓更多人知道 FactoryBot 的好處及妙用。