根據上一篇「測試效能最佳化」裡提到的一個概念
減少不必要的 DB 操作來提高測試效率
而其中做了以下幾件事
- 每支測試檔案,只做一次 clear DB 確保測試檔案之間不互相影響
- 可以使用一個數據進行測試,就不要每個 example 重複創建,減少 let and let! 大量使用實例變量
- 情境判斷,盡可能使用 mock 數據方式,達到你要的測試結果
- 使用 FactoryGirl.build orFactoryGirl.build_stubbed
前 3 點,已經熟能生巧
第 4 點,是我一直抱著 try try 看去實做
而今天偶然發現這兩個差別,做筆記分享一下
FactoryGirl
Rspec 使用這個 FactoryGirl大家應該不陌生
或許你的專案,是用基於 FactoryGirl 之上的 FactoryBot
關於 FactoryGirl.create
1 | [1]pry(main)> user = FactoryGirl.create(:user) |
可以發現對 DB 有一次 commit,創建一筆 User 實體數據
所以你的測試第一行如果是
1 | let(:user) { create(:user, rich)} |
假設 user_spec.rb
有 100 個 example
進行這個測試檔案的過程,你會真實得到 100 個不同的 user。
如果你還設定在每一次的 example 做 DatabaseCleaner
1 | config.after(:each) do |
恭喜你還會清除這 100 筆資料
等於跑一次測試,背後進行了 200 次的 DB commit
關於 FactoryGirl.build
1 | [1]pry(main)> order = FactoryGirl.build(:order) |
假設你的 Factory::order 有以下兩個 association
1 | factory :order do |
確實得到 order id 為 nil,且並沒有存進數據庫裡
但是…不巧的是,這裡還是有 DB commit,因為埋了一個伏筆:代碼裡 Factory::user搭配 :rich會進行 update,也就是會 save
所以 user 還是一筆真實數據存入 DB (心裡OS: !!E@!#$@)
總結來說:
build 一個 order 會創建其相關的 association
理所當然 order.user 會是一個 id 為 nil 的數據
應用測試場景像是進行 model 層的 validation 驗證就可以使用 build
1 | expect { order.valid? }.to eql(fasle) |
關於 FactoryGirl.build_stubbed
1 | [1]pry(main)> order = FactoryGirl.build_stubbbed(:order) |
可以發現快速的得到一個 order,也就是既有 id 也有 created_at 等等…
是一個極真的實例,但沒有任何 DB commit
但是實際查相關 association(order.user)
是完全沒有數據的
所以跟使用 FactoryGirl.build
相比,就顯而易見其差異性
如果在你的測試環節裡,是會用 association 去獲取相關真實數據的話
假設付款後,預期會同時創建一筆發票記錄,就建議別使用 build_stubbed
當然想使用複雜的 mock 技巧,那就另當別論~
結論
所以在大部分場景裡,建議如果 build 可以跑過測試,可以嘗試使用 build_stubbed,這是真的可以提高測試效率的,就在我某支測試檔案上擁有 11 個 example 儘管只跑了 8 秒,仍可以減少 4 秒,夠厲害了!
再找 build_stubbed 時,網路上查到前幾筆資料也都跟提升測試效能有關!
所以滿建議大家有機會玩玩看,親自查查相關資料佐證
Ref
- FactoryGirl (Bot) - create vs build vs build_stubbed
- Use Factory Girl’s build_stubbed for a Faster Test Suite
Factory Bot cheatsheet: