如何寫一個好的測試
部落格原文地址
背景
在上一個專案上,由於專案成員大部分是新入職的同事,所以對於測試不是很熟悉,
這就導致了在專案前期,專案上的很多測試都不太make sense,雖然沒有什麼定量的東西來描述,
但是總結起來就2個點:
- 測試的名字比較模糊。
- 測試程式碼不易讀。
深入剖析
測試名字比較模糊
對於這一個問題,是因為很多剛開始寫測試的開發腦子裡不會快就想到given、when、then這三個詞,
一般我們寫測試寫得比較多的同事,都會使用should_return_xxxx_when(if)_xxxx
。其實就是在腦子中
想到了given、when、then。而新同事照著模仿的時候,很可能就單純地寫了should_xxx
但是沒有考慮條件,這就導致了讀名字還是get不到測試想要幹什麼。
對於這個問題,我想起了多年前在stackoverflow上面看到的一種寫測試名字的模板,他是這樣推薦的:
GivenA_WhenB_C
,我覺得這中寫法挺好的,所以在專案中用了起來,結合實際使用,我又對此提出了兩個改進:
- given when這兩個詞可以省略,當然前提是我們約定了最前面就是given,中間就是when,最後就是then。
通過施加規則限制來縮短測試名。 - 測試名字常常很長,一大堆駝峰其實比較不容易閱讀。所以可以使用蛇形命名法,但是這樣就需要想一個符號來
分隔given、when、then了,我選擇使用___
_
),因為經過試驗,發現2個_
太短了不容易一眼看出分隔
4個則又沒有必要。
最終的效果是這樣的(這是一個例子,來自最近我參加的DDD進階培訓中的訓練題):
parking_order_is_natural_order___park_cars_by_parking_assistant___car_parked_to_correct_parking_lot_in_turn car_is_already_took___take_back_car_with_used_receipt___exception_is_thrown parking_lot_is_available___park_a_car_by_parking_assistant___receipt_returned car_is_parked___take_back_car_with_invalid_receipt___exception_is_thrown
這裡還有一個小技巧,如果一個測試真的沒有given的時候,或given不重要的時候,可以省略,但是___
不能省略:
___xxx_xxx___xxx_xxx
可以總結一下這種命名方式的優點:
- 能制定一個規則的話,專案上的測試標題能更容易統一(可以說是統一語言了)。還可以加上靜態檢查,使得一些名字不規範的測試提前被發現
名字不規範,說明是新進專案的同事寫的,確實要重點檢查一下。 - 強制規定出given、when、then,那麼,我們在寫測試的時候,就會被強迫想清楚我們的測試要做什麼。
- 結構化的東西更適合大腦閱讀,讀測試的時候更容易,我們不需要先讀一遍測試名才能提取到given、when、then,可以一眼就定位出三個部分。
當然,也是有缺點的:
我用這種方式寫出來的測試名字一般都比較長,這個有可能是我用詞還不夠精煉,在given、when的部分可能有時候是有一部分重複的
所以也需要刻意練習,學會精簡用詞。
最後,給這種命名方式命個名吧,就叫GWT測試命名法好了。
測試程式碼不易讀
我在專案上發現,很多人習慣在構建fake資料的時候直接將所有資訊遮蔽,就提供一個createX()的方法,
在createX的方法裡面可能還要構建X的構成部分:
X createX() {
Y yyy = new Y;
X xxx = new X(YYY, field1, field12);
return xxx;
}
除了createX外還有createAX、createBX,然後在不同的測試裡面混合呼叫。
作為看測試的我,我就很難看到到底需要一個怎樣的場景,field1到底是怎麼設定的,field2到底是怎麼設定
的,而且在想改一下createX的時候,還會牽扯到其他的測試莫名其妙就掛了。
我目前使用的解決方式是這樣的:
先要確定好測試要涉及到的重要資訊,比如上面如果field1,field2都會對測試的邏輯起到作用,
那麼,即使冗餘,也一定要寫在測試方法內。比如:@Test void ___xxx___xxx() { Y y = createY(); X x = createX(field1, field12, y) // do some thing // assert some thing }
這麼做的好處是,當我要看某一個測試的時候,它的前置資料我一眼就能看出來,不需要不停地command+b跟進去才知道需要什麼。
在應用第一條原則的時候,一定要記得,只羅列出這個測試所關心的資料。假如field3完全不參與這次
邏輯的處理,又必須要有值,那麼,在createX內部給個預設值即可,不需要放在createX引數列表中。
為什麼不適用builder?
其實之前也試過用builder,但是看起來太多了,適用create的方式能縮短要寫得程式碼行數,更容易一眼看完測試