單元測試
何謂單元測試
單元測試的定義
單元測試在早期使用 Smalltalk 程式語言進行開發時便已存在。單元測試是讓開發者提升程式品質、加深理解程式方法和類別的最佳方法之一。
一個優秀的單完測試為一段自動化的程式碼,這段程式碼會呼叫待測試的工作單元,並對這個單元的單一執行結果的假設進行驗證。單元測試往往會透過單元測試框架進行撰寫。單元測試需要很容易進行撰寫、執行快速,執行結果可靠,程式碼易讀並且容易維護。只要程式碼沒有改動,測試結果都會是一致的。
工作單元指的是從呼叫系統的一個公開方法到產生一個測試可見的最終結果的執行期間所發生的行為。測試可見的最終結果代表的是只需要透過公開的系統 API 就可以觀察到的結果,並不需要知道程式碼的內部狀態。常見形式如:
- 被呼叫的公開方法回傳的回傳值
- 呼叫方法的前後,系統可見的狀態或行為發生變化,這些變化不需要透過查詢私有狀態就可以取得和判斷
- 呼叫不受測試的第三方系統,該第三方系統不會有回傳值或是回傳值不會拿來使用
註,被測試的的對象又稱 System Under Test, SUT,被測試的系統
工作單元大至包含特定功能的許多類別和函數,小至一個方法。越大的工作單元可以表達 API 對於使用者的能見度越高,因此而較好維護。然而切得過小的工作單元容易產生需要偽造許多中間狀態的物件,這些物件並非公開 API 真實會產生的最終結果,這是一種 over specification。
好的單元測試可以省掉日後的維護成本避免浪費時間花在小細節除錯,並且可以提高程式的品質。然而不好的單元測試卻可能讓你花掉大量的時間在撰寫測試然而卻要花更大量的時間維護它,甚至最終又回到使用手動測試。
單元測試的特性
開發人員在程式撰寫時都會進行過單元測試,不論是在程式碼中加入 print 將物件狀態、方法執行結果輸出,或是撰寫 GUI 介面時手動填入表單的內容並檢查是否如預期執行欄位檢驗,然而這些測試的方法有著幾個可能的缺點,如無自動化、不同時間點執行的結果可能會不一致、不同使用者執行的結果可能會不一致、測試不容易被實現等等。單元測試會有著以下的特性:
- 自動化且可以被重複執行
- 容易被實現
- 非臨時性的,不論什麼時間點都有存在的意義
- 任何人都可以輕易執行
- 執行快速
- 執行結果應該一致
- 可以完全掌控測試單元
- 完全隔離的,與其他測試無相依、獨立於其他測試
- 簡單清楚說明測試失敗的原因及預期的結果應為何
這些特性可以透過下列五個問題檢視自己的測試是否是單元測試:
- 幾週前、幾個月前、幾年前撰寫的測試是否至今都可以正常執行且得到結果?不論在何時,在修改程式後,若不能對之前所有功能進行測試,則容易產生不經意的 bug改東壞西,因此單元測試在此的效益便是在修改後快速確認是否既有的程式在工作單位的範圍下行為正常不變,參照Working with Legacy Code,回歸測試則是用於測試程式整體行為是否與過去行為一致,不論是一個或是多個工作單元。
- 幾週前、幾個月前、幾年前撰寫的測試是否至今任何人執行都可以正常執行且得到結果?在多人合作之下,需要保證自己所做的修改不會弄壞他人的程式與功能,因此單元測試可以協助開發者確認自己修改的部分是否對其他程式產生破壞,確認相依的程式正常運行。
- 是否可以在幾分鐘內跑完所有的單元測試?若不能快速的完成所有測試,便不會經常執行這些測試,然而我們希望快速的得到修改程式後的回饋以確保自己沒有破壞或執行是否如預期,然而若回饋的間隔隔越久則期間產生問題的原因點則可能越多。
- 是否可以一鍵執行所有的單元測試?若有尚未自動化的單元測試則可能在重複測試時被跳過這些手動設定的測試,然而在開發功能繁忙的任務下,普遍不便去或不想進行額外設定,進而讓特定的區塊容易產生問題。
- 是否可以幾分鐘內撰寫一個單元測試?與整合測試不同的是不會需要額外設定環境和準備才能夠進行測試,當撰寫的難度越高則願意撰寫的的自動測試便越低,只注意大問題卻忽略小問題,然而單元測試往往可以將枝微末節的問題覆蓋並抓出。