之前有介紹過 before hooks
的使用方法,今天也可以介紹一下 after hooks
的使用方法,直接使用範例看看!
After Hooks
RSpec.describe "before and after hooks" do
before(:example) do
p "before hooks"
end
after(:example) do
p "after hooks"
end
it "test for before & after" do
p "inner example"
expect(1 + 1).to eq(2)
end
end
下面這是 Output 的結果,可以看到執行順序的不同!
那一定會很好奇,什麼時候要使用 after hooks
,有時候為了效能考慮,工程師們會不想要每次都產生一個新的物件。
所以會在 after hooks
中做把物件 Reset 資料或是清空資料庫等等的動作!
可以用一個簡單的範例來看看
RSpec.describe "before and after hooks" do
before(:example) do
end
after(:example) do
p @array
p @array.clear
end
it "test for before & after" do
@array = []
p "Inner #{@array}"
@array << '1'
expect(1 + 1).to eq(2)
end
end
我們在測試中產生一個 @array
然後在 after hooks
中清空它,下面是 output 的樣子!
可以看到 @array
在範例結束後被清除了,這對於某些情況是有幫助的,但使用的程度並沒有到極高的比例,還是可以把它記起來,說不定哪天會派上用場!
我自己還是比較喜歡在每個例子前產生物件,所以最終還是取決於團隊的決定和方向
Before & After Hooks 的選項
剛剛介紹完 after hooks
的運作模式,接著要介紹一下傳入的參數所造成的差別!
一般都是預設在 (:each)
的情形,也就是每一個 example
都跑一次的意思!
這邊介紹一個 (:context)
的選項,可以讓你在 describe
& context
的前後執行 hooks,來看看例子吧!
RSpec.describe "before and after hooks" do
before(:context) do
p "before context"
end
before(:example) do
p "before example"
end
after(:example) do
p "after example"
end
after(:context) do
p "after context"
end
it "test for before & after" do
expect(1 + 1).to eq(2)
end
end
接著看 output 會非常明顯和清楚!
(:context)
會在整個測試最開始的時候執行一次,然後會在整個測試結束的時候執行!
這個的用處在於,某些設定只需要一次,就可以在每個測試裡面拿到,而不需要每一個 example 都產生一次,這樣在效能上就會有很大的差別。
所以寫測試的時候,稍微考慮一下是不是都會被使用到而且又剛好都不會被改變,如果答案是確定的!那就可以使用 (:context)
的選項來幫助你!
箝套的邏輯和順序
熟悉的兩個 hooks 的運作模式之後,我們要來把 hooks 加入箝套裡面測試一下自己的理解是不是正確的
所以先上程式碼 ( 可以自己先想一下答案 )
RSpec.describe "before and after hooks" do
before(:context) do
p "OUTER Before context"
end
before(:example) do
p "OUTER Before example"
end
it "will pass" do
expect(1 + 1).to eq(2)
end
context "when it can pass" do
before(:context) do
p "INNER Before context"
end
before(:example) do
p "INNER Before example"
end
it "will pass too" do
expect(2 + 2).to eq(4)
end
end
end
有辦法一步步地推敲出正確答案的話,代表已經完全理解了 hooks 的運作順序。
需要去思考測試碼是由上至下的讀取,所以會先經過什麼?遇到什麼又會觸發什麼?
只要能一步一步地釐清其實就不會感覺非常複雜
公佈答案:
最容易沒想到的地方就是在 context 裡面的 example 也吃到了外面的 before hooks!
最外層的物件,在裡面也是拿得到的,這對於一開始寫 RSpec 還蠻有幫助的。
因為在一邊整理程式碼的時候,會出現這個物件會不會拿不到,要不要再寫一個 let
等等的疑問
那至於為什麼要在不同的地方寫 hooks 呢?
是因為某些 Context 可能有屬於它的物件需要放在裡面,若是所有的東西都放在最外層,很有可能不小心就取到相同的名字,或是看起來很雜亂!
雖說寫在最外面肯定是不會有什麼問題,但能夠精準的物件放在他的作用域,也是一個工程師厲不厲害的指標!
利用作用域來覆寫物件
剛剛提到了內層可以取得外層的物件,但如果我想要在 context 裡面測試同一個物件,不同的行為怎麼做呢?
先用個範例來看看:
class Burger
attr_accessor :type
def initialize(type = "small")
@type = type
end
end
RSpec.describe Burger do
let(:burger) { Burger.new("Big") }
it "type will be Big" do
expect(burger.type).to eq("Big")
end
context "when we didn't give argument" do
let(:burger) { Burger.new } # 利用覆寫來通過測試
it "type will be small" do
expect(burger.type).to eq("small")
end
end
end
當我們想要給一個 context 的情境來測試沒有傳入參數的預設值時,要如何可以通過測試?
我們可以利用覆寫 burger 這個物件的內容,來通過測試。
當測試碼跑到第 18 行時,他會從自己的作用域開始找尋一個叫做 burger 的物件,若是沒有就會往上去找!
所以不覆寫的話,會找到第 9 行的 burger 物件,導致沒辦法通過測試。
所以當我們理解了作用域,就可以隨心所欲的撰寫測試,根據不同的情境加入不同的條件!
而且使用 let
也不擔心效能的問題,還記得我們在介紹 let
的時候提過的 lazy-loading 嗎?
結語
今天介紹了 RSpec 的作用域,以及覆寫帶來的幫助,希望可以幫到像我一樣的新手。
畢竟測試也不是超基本的知識,還是需要自己去讀文件和練習才會進步!
所以希望自己的想法可以有所幫助!
明天會介紹 subject
這個物件,也會介紹縮短程式碼的方法 ( Ruby 的特性就是越簡潔越好 )