單元測試的藝術-摘要
之前在博客來的新書介紹看到這本書,看了一下目錄和工具介紹,書中示範的程式碼是用C#,工具介紹的工具我都沒用過,就想說算了。最近公司有買這本書就拿來翻翻看,還真的和Bob大叔說一樣XDD,看到一些我大概知道但又不是很確定的內容,可能是之前從程式碼中學習,但沒有很了解用法,為什麼要這樣設計。
不管你是對單元測試或是測試驅動開發的新手,還是已經有豐富經驗的人,都能在這本書裡找到適合自己的內容。 — — 《無瑕的程式碼》作者 Robert C. Martin(Bob大叔)
來寫一些印象比較深和理解的內容,本身是寫Android的,平常會使用的測試框架為Junit和Mockito,UI層會用Espresso。這本書大部分是介紹觀念和用法,並沒有很多的程式碼,我會針對我理解的內容來寫,寫起來會比較像是筆記或是摘要的形式:
單元測試基礎
單元測試應該具備以下特質:
1.可自動化,可重複執行
2.很容易被實現
3.不是臨時性的
4.執行速度應該很快
5.執行結果應該一致
6.應該是完全隔離的
7.可以被任何人執行和存取
什麼是優秀的單元測試?一個單元測試是一段自動化的程式碼,這段程式會呼叫被測試的工作單元,之後對這個單元的單一最終結果的某些假設或期望進行驗證,單元測試幾乎都是使用單元測試框架進行撰寫的。撰寫單元測試很容易,執行起來快速,單元測試可靠,易讀,並且很容易維護,只要產品程式碼不發生改變,單元測試的結果是穩定一致的。
並提到了整合測試是對一個工作單元進行測試,而這個測試對被測試的單元並沒有完全的控制,而是使用該單元一個或多個真實依賴的相依物件,例如物件、網路、資料庫、執行緒、亂數產生器。
忘記這張圖在哪邊看到了,真的是很有趣的比喻,單元測試做完還是需要整合測試來確保整體的功能,想到高品質程式這本書介紹到的測試三角型。不過本書的焦點還是和書名一樣著重在單元測試的部分。
第一個單元測試
書中提到大部分的讀者所做的測試都有某些限制:
- 非結構化測試
- 無法重構執行
- 測試沒有覆蓋到重要的部分
這時候就需要測試框架的幫助了,各種語言可能都有屬於自己的xUnit,Java的是JUnit,.NET的是NUnit,Haskell的是HUnit。
測試方法的名稱名也是很有學問的,測試方法名稱分為三個部分:
1.UnitOfWorkName:被測試的部分,一組方法或一則類別
2.Scenario:測試進行的假設條件,ex:登入失敗、無效的使用者
3.ExpectedBehavior:在測試情境指定的假數條件下,你對被測試方法行為的預期,行為有三種,回傳值、系統狀態改變、呼叫外部第三方系統提供服務。舉例來說這個命名: isValidLogFileName_BadExtension_ReturnFalse(),要測試這個 isValidLogFileName這個方法,在BadExtension的條件下,會回傳false,這樣看測試方法就可以一目瞭然,測試是在測什麼。
一個單元測試包含了三個行為(3A):
1.準備(Arrange)物件:建立物件,進行設定
2.操作(Act)物件
3.驗證(Assert):某件事符合預期
使用參數化來進行測試,傳入各種條件和預期結果,可以測試個多邊界條件,但也不用為了測試邊界,而寫很多很像的code。
Setup和Teardown為每個測試方法前後(分別)執行的動作。作者偏向使用工廠模式來初始化物件,而不是使用Setup,Teardown也很少用到,用到可能是有相依資料庫,或是Singleton的狀態。作者認為Setup和Teardown會讓可讀性變得更差,因為單看一個測試方法時,可能會不知道準備(Arrange)物件這個動作到底做了什麼,還要回去看Setup寫了什麼。不過自已用起來是覺得習慣後就知道去哪邊找,也要看整個團隊寫測試的習慣。
盡量不要使用ExceptException,因為不知道是在哪邊發生Exception,有可能是建構的時候發生的,推薦使用Assert.Catch直接驗證會有較準確的結果,而且發生Exception後,後面寫的驗證(Assert)條件式也不會繼續執行。在JUnit也有相對應的寫法。
透過虛設常式解決依賴問題
外部依賴(external dependency)是指系統中的一個物件,與被測試程式碼互動,但你卻無法掌握( 執行緒、記憶體、時間),上述提到ExpectedBehavior的其中一種(系統狀態改變)。
虛設常式(stub):是系統中產生一個可控的替代物件,來取代一個外部相依物件,可在測試過程中,透過虛設常式來避免直接相依物件造成的問題。和mock類似,但會對mock進行驗證,不會對虛設常式進行驗證。
抑制測試(test-inhibiting:當程式碼依賴於外部資源,程式碼邏輯正確,但這種依賴還是有可能導致測試失敗,可以加入一層中介層來解決。
1.找到被測試物件所使用的介面或API
2.把介面的底層實作替換成你能模擬的
需要更多的步驟:
1.找到導致被測試的工作單元無法順利測試的介面
2.如果被測試的工作單元是直接相依這個介面
3.將這個相依的介面的底層實作內容換成可以控制的程式碼
重構設計以提升程式碼的可測性,重構是在不改變程式碼的功能的前提下,修改程式碼的動作。接縫(seam)是指在程式碼中可以抽換不同功能的地方,ex:使用虛設常式類別,增加一個建構函式參數,把一個方法改成可供使用虛擬方法,透過實作開放封閉原側,對擴充開放,不能直接修改功能內部實作。
可以使用依賴注入:在被測試單元中注入一個假的實作內容
1.在建構式中得到一個介面的物件,儲存到field
2.在屬性的get或set方法中得到一個物件
3.透過以下方式,在測試前獲得一個介面的假物件方法的參數(參數注入)、工廠類別、區域工廠
使用模擬物件(Mock)驗證互動
測試與相依物件之間的互動,有三種:基於值、狀態與互動的測試,盡量選擇前兩種,互動測試會讓事情變得複雜。
互動測試是針對一個物件下如何向其他物件發送訊息(呼叫方法)的測試,如果一個特定的工作單元的最終結果,是呼叫另外一個物件,就要進行互動的測試模擬物件事系統中的假物件,可以拿來驗證被測試物件是否如預期般呼叫這個物件,因此來使得單元測試執行成功或失敗,通常每個測試只會有一個模擬物件。
假物件是通用的詞,可以用來當虛設常式物件(沒有互動),模擬物件(驗證物件間的互動),兩者最根本的差異:虛設常式物件不會導致測試失敗,而模擬物件可以。
由上圖中的介紹,就可以清楚地知道兩種物件在測試扮演的角色與功能。
隔離(模擬)框架
可用來幫助寫程式的API,使用這套API來建立假物件,比手刻物件快、容易,像是Mockito,就可以很容易建立假物件。
準備-執行-驗證(arrange-act-assert),準備階段,建立和設定假物件,接著對被測試產品執行動作,最後在結尾的部分驗證在測試中是否正常的與假物件進行互動。
隔離框架的優缺點
1.更容易驗證參數
2.更容易驗證一個方法被多次呼叫
3.更容易建立假物件
缺點:
1.可讀性變差
2.驗證了錯誤的東西
3.一個測試有多個模擬物件
4.過度指定的測試
測試階層和組織
使用繼承類別繼承模式,支持物件導向程式設計最有利的理由之一是:你可以重用現有的功能,不必在別的類別一次又一次的重新設計,DRY(Don’t Repeat Yourself),設計基本類別:
1.重用輔助方法和工廠方法
2.不同類別上執行同一組測試
3.使用共用的Setup和Teardown
好的單元測試的支柱
應該具備下列三項特色:1.可信賴的 2.可維護的 3.可讀性
具可維護性的設計來使用SetUp方法
有些Bad smell代表可能有些問題:
1.強制的測試順序:測試框架不能保證測試執行順序
2.存在呼叫其他測試的隱藏動作
3.共享狀態損壞
4.外部共享狀態損壞
一個驗證語句拋出例外,測試方法中其他程式碼就不再往下執行了,必須保證所有的驗證都必須被執行,可以改進的方式:
1.每個驗證建立一個單獨的測試
2.使用參數化測試(可讀性較好,相比try catch)
3.把驗證的程式碼放在一個try catch區塊中
物件比較,不使用多次驗證,而是建立一個用來比較的物件,物件要複寫equal才能比較,複寫toString輸入有用的資訊。
避免過度指定:
1.指定驗證被測試物件的純內部行為:測試的程式碼不屬於任何公開契約或介面
2.在需要虛設常式物件時,使用模擬物件
3.不必要的順序或過於精準的參數匹配:使用contain比equal好
Setup和Teardown 常常被濫用,由測試方法自己直接初始化建立模擬物件,設定所有的預期結果,可讀性會較好。
在組織中導入單元測試
看到這篇覺得蠻特別的,除了教怎麼做測試,還教怎麼應用到工作團隊上,如果團隊中只有一個人有寫測試,那測試的覆蓋率不會高,效果也不會好,那要怎麼說服其他人,看完覺得比寫測試還難XDD
之前有聽過說寫測試,那就不就不用QA啦,想一想就覺得不可能,測試是寫測試的人針對他預期的結果去寫,如果預期的結果就是錯的,那怎麼測都還不對,充其量也只是把事情做對,而不是做對的事。更不用說一些有使用者介面(像手機APP),複雜的操作情況了,必須要有手動的方式來進行測試。
單元測試提供了抵抗bug的第一層防護,QA工作提供了第二層:使用者驗收層。
遺留程式碼
這篇看了比較沒有共鳴,本身還沒有對很古早的code寫過測試XD,有找到一篇[遛書]《單元測試的藝術》測試可行性表,針對這張做比較細部的介紹
設計與可測試性
單元測試具有下列一些特色:
1.執行速度快
2.相互隔離,即每個測試可以獨立執行,可以按照任何順序
3.不需要額外外部的設定
4.產生穩定可靠的測試結果,包含通知和失敗
看完這本書釐清了一些測試的觀念和名詞,還有心法,以及如何在應用在職場上,覺得值得買一本來收藏!