蒼時弦也
蒼時弦也
資深軟體工程師
發表於

優雅的 RSpec 測試 - 前置處理

這篇文章是 優雅的 RSpec 測試 系列的一部分。

在上一個段落中,我們有提到需要限制 Let 的使用,然而當我們有比較複雜的測試前置條件時,就很難避免撰寫過於複雜的測試主題,此時就可以利用「前置處理」的機制來解決這個問題。

Before

Before 通常會在一個測試案例前執行,因此我們可以用來設定一些測試資料的建立。舉例來說,我們希望在一個訂單中預先加入商品,然後用來驗證這筆訂單是否符合預期,就可以使用 Before 來進行處理。

 1RSpec.describe Order do
 2  subject(:order) { Order.new }
 3
 4  before do
 5    order.add_item(OrderItem.new(amount: 100))
 6    order.add_item(OrderItem.new(amount: 100))
 7  end
 8
 9  it { is_expected.to have_attributes(subtotal: 200) }
10  it { is_expected.to have_attributes(items_count: 2) }
11end

以上面的例子,我們會在開始執行 it 所給予的測試案例前,先呼叫 Order#add_item 來加入兩個金額為 100 的品項,當我們正式執行 it 來驗證時,就不會是一個什麼都沒有的 Order 物件,而是已經跟兩個 OrderItem 有所關聯的物件,因此就能夠用來驗證 #subtotal#items_count 兩個方法是否正確。

After

跟 Before 不同的地方是 After 是測試案例執行結束後才被執行,通常我們會用來清理 Before 所產生的一些暫時性資料,或者將一些開關切換回去。

 1RSpec.describe Worker do
 2  subject(:worker) { Worker.new { true } }
 3
 4  describe "#execute" do
 5    subject { worker.execute }
 6
 7    before { Worker.mode = :inline }
 8    after { Worker.mode = :async }
 9
10    it { is_expected.to be_truthy }
11  end
12end

Around

因為 After 很多時候都是跟著 Before 一起搭配使用,因此我們可以改用 Around 來處理,這樣在一些暫存檔案之類的處理,我們就不需要另外放一個 Let 來紀錄暫存檔案。

 1RSpec.describe Parser do
 2  subject(:parser) { Parser.new }
 3
 4  describe '#parse' do
 5    subject { parser.parse }
 6
 7    around do |example|
 8      temp_data = Tempfile.new
 9      temp_data.write('{ "data": "example" }')
10      temp_data.rewind
11      parser.add(temp_data)
12
13      example.run
14
15      temp_data.unlink
16    end
17
18    it { is_expected.to include('data' => 'example')}
19  end
20end

使用 Around 的時候我們可以拿到 example 作為測試案例對應的物件,只需要在我們處理完前置條件後呼叫 example.run 就可以執行測試,並且在後續進行對測試前置處理的清理動作,可以根據情境判斷使用 Around 還是個別呼叫 Before / After 來對測試進行預先準備。

Hook Type

在預設的狀況下我們的 Hook(鉤)是針對單一測試案例的,然而有時候我們希望一次性的針對整個測試、測試群組來設定,就可以利用指定 suiteall 的選項來設定觸發的時機點。

 1RSpec.describe Worker do
 2  around(:suite) do |example|
 3    Worker.inline! { example.run }
 4  end
 5
 6  context 'when worker not in parallel' do
 7    before(:all) { Worker.max_job = 1 }
 8
 9    # ...
10  end
11end

以上面的例子,我們希望整個測試都使用 Worker.inline! 模式為前提處理,同時在特定的情況下,限制不要平行處理,這樣我們就不用在每一個測試案例都重複的呼叫這些設定變更,在測試案例非常多的狀況下,可以有效地改善執行的速度。


如果想在第一時間收到更新,歡迎訂閱弦而時習之在這系列文章更新時收到通知,如果有希望了解的知識,可以利用優雅的 RSpec 測試回饋表單告訴我。