Website logo

Robert Chang

技術部落格

RSpec - 如何使用 Factories 以及 Fixtures

測試有一個必要的條件就是需要有假資料來做測試,有三種方法可以在 Rails 中產生測試資料。

  1. 手動
  2. Fixtures
  3. Factory-Bot

這三種方法都是可行的解決方案,可以一個一個來探討一下。

手動建立資料

如果測試的 Model 只有幾個屬性,而且沒有關聯的 Model,手動建立測試資料算是非常方便的選擇。

假設,有一個 PaymentType Model,只有一個屬性,名字。而且也可以很輕易地辨別出有效以及無效的狀態,很簡單:

valid_payment_type = PaymentType.new(name: 'Visa')
invalid_payment_type = PaymentType.new(name: '')

接著,有一筆訂單,它是由多個 LineItems 和 Payments 組成,每個都有一個 PaymentType。

order = Order.create!(
  line_items: [
    LineItem.create!(name: 'PS5', price: 15000)
  ],
  payments: [
    Payment.create!(
      amount: 15000,
      payment_method: PaymentType.create!(name: 'Visa')
    )
  ]
)

其實這樣寫起來其實超級煩的,不得不浪費一堆腦力,想一些不一定相關的細節。

如果想要測試的是,amount 等於 line_items 的總額時,order.balance 回傳 0,該怎麼做?

接這就是 Factory 派上用場的時候了!

Factories

Factory 其實不是一個只存在於測試的概念,Factory Method 是一種設計模式,可以在 Gang of Four 這本書中找到,而這個模式恰好對測試的目的很有用。

Factory 的概念基本上,就是有一個方法或是函數,可以為相同的類別產生不同屬性的實體。

Gang of FourDesign Patterns 這本書中,其舉了一個例子,一個叫做 Creator 的工廠會根據不同的條件回傳 MyProduct 或是 YourProduct 的實體。可以直接說 Creator.create(相關資料),就會自然回傳適當的實體!

你也許會想這樣的 Factory 有什麼用處,在測試的情況下,我們想要的 Factory 類型可能會有一點不同。我們關心的不是抽象的類別實體化。我們希望抽出沒意義的細節和複雜的 Model 關聯。

下面是一個例子,說明如果我們有一個 Factory,剛剛 Order 的設置會是怎麼樣呢?( 關於 Factory-Bot 在之前的文章有做過分享基本的用法 )

order = create(
  :order,
  line_items: [create(:line_item, price: 15000)],
  payments: [create(:payment, amount: 15000)]
)

在這樣的情況下,我們只需要專注在要測試的項目,不用像剛剛手動的方式去關心一些奇怪的名目和支付方式,做好我們關心的 priceamount 就可以了。

那要怎麼用 fixtures 來實現同樣的事情呢?

Fixtures

首先我對於 fixtures 的經驗和 Factory-Bot 相比,比較不熟。但可以看看這篇關於 fixtures 的文章!

說到這裡,如果想要做出和上面 Factory-Bot 一樣的測試資料,也可以使用 fixtures 來做,通常情況下,參考別人都是用 YAML 檔的形式來表達!

# orders.yml
payments_equal_line_item_total:
  # no attributes needed

# line_items.yml
ps5:
  order: payments_equal_line_item_total
  name: 'PS5'
  price: 15000

# payment_methods.yml
visa:
  name: 'Visa'

# payments.yml
first:
  order: payments_equal_line_item_total
  payment_method: visa
  amount: 15000

一但確定好 fixtures 的資料後,用它就像在用 Hash 一樣簡單:

order = orders(:payments_equal_line_item_total)

所以,你覺得哪一種生產測試資料的方式好用呢?

結語

一開始就已經證明,手動產生測試資料這件事情真的很麻煩,但是,也不是真的不好用,其實在自己的 side project 裡面就很適合,手動生產就不需要開檔案,分那麼多的資料夾,而且可讀性又提高,東西都在一個檔案裡面。

至於 fixtures 和 Factory-Bot,我自己是用 Factory-Bot 居多,主要原因是 Gem 的強大;而關於 fixtures 的 YAML 檔,雖然很好讀,但我自己還是不太習慣。

至於之前看到有人提過對於 fixtures 的見解,其認為,一個專案會創造一個 資料世界,然後用這個很大又很複雜的世界作為測試的基礎,發文的人說他更傾向於在可行的情況下,從一個乾淨的 Model 開始每一個測試,並為每個測試產生該測試所需要的最低限度資料,他發現,這讓測試變得容易理解,而不是用 fixtures 寫死固定的測試資料。

當然也不是說只能挑一個用,如果今天你想要有一個固定的回傳資料( 可能是金流,那種從來不會改變的東西 ),也可以引用 fixtures 然後傳遞到 Factory-Bot,在整個測試資料上產生不同的交互作用。

自己還是大推 Factory-Bot 啦,但可能是我對於 fixtures 的理解不夠深,說不定一年過後就會推翻這些自己覺得是對的東西,那也沒關係啦,繼續變強才是重點嘛。

上一篇文章RSpec - 如何撰寫 Characterization Test?

下一篇文章RSpec - 五個自動化測試的好處