給軟體工程師的12條單體測試的建議
單體測試是敏捷軟體開發的支柱之一。最早由Kent Beck提出,如今單體測試已經進入了很多組織的系統和核心。單體測試幫助工程師減少缺陷數目和除錯時間並有助於開發更健康、更穩定軟體。
在本文中我們將探討軟體工程師在任何程式語言或環境中都可以應用的12條單體測試建議。
1 使用單體測試管理風險
一個新手可能會問為什麼我要寫單體測試?的確,難道測試不是軟體工程師都想外包給QA人員的枯燥的東西嗎?這是一個在現代軟體工程中不復存在的思維定勢。軟體團隊的目標是開發出最高質量的軟體。消費者和商務使用者可能在80年代和90年代還能容忍“蹩腳”的軟體。但隨著大量庫、web服務以及支援重構和單體測試的整合開發環境的出現,現在已經無法為軟體的缺陷找藉口了。
單體測試背後的思想就是為每個軟體元件建立一套測試。單體測試使得持續軟體測試(continuous software testing)成為可能,因為較之手工測試,重複執行它們很廉價。
在你的系統擴充套件的同時,單體測試也不斷擴充套件。每個測試都是系統可以執行的保證。程式碼中的缺陷就意味著風險。利用單體測試集,工程師可以使得缺陷數目和未測程式碼的風險大大降低。
一個新手可能會問為什麼我要寫單體測試?的確,難道測試不是軟體工程師都想外包給QA人員的枯燥的東西嗎?這是一個在現代軟體工程中不復存在的思維定勢。軟體團隊的目標是開發出最高質量的軟體。消費者和商務使用者可能在80年代和90年代還能容忍“蹩腳”的軟體。但隨著大量庫、web服務以及支援重構和單體測試的整合開發環境的出現,現在已經無法為軟體的缺陷找藉口了。
單體測試背後的思想就是為每個軟體元件建立一套測試。單體測試使得持續軟體測試(continuous software testing)成為可能,因為較之手工測試,重複執行它們很廉價。
在你的系統擴充套件的同時,單體測試也不斷擴充套件。每個測試都是系統可以執行的保證。程式碼中的缺陷就意味著風險。利用單體測試集,工程師可以使得缺陷數目和未測程式碼的風險大大降低。
2 為每個重要元件寫一個測試用例(Test Case)
在你開始寫單體測試的時候,試問自己我需要些什麼樣的測試?
最初的動機可能是寫一堆功能測試。比如,檢查系統不能功能的測試。這是不對的。正確是做法是為每個重要元件建立一個測試案例(一套測試)。
每次測試的關注是單個元件。在每個元件內部尋找一個介面——該元件公開暴露的行為的集合。你必須為每個公開的方法寫至少一個測試。
在你開始寫單體測試的時候,試問自己我需要些什麼樣的測試?
最初的動機可能是寫一堆功能測試。比如,檢查系統不能功能的測試。這是不對的。正確是做法是為每個重要元件建立一個測試案例(一套測試)。
每次測試的關注是單個元件。在每個元件內部尋找一個介面——該元件公開暴露的行為的集合。你必須為每個公開的方法寫至少一個測試。
3 建立虛的測試用例(Test Case)和測試工具(Test Utilities)
對於任何程式碼都可能有所有測試都需要做的共通的東西。首先針對開發語言選擇一個測試框架。比如,在Java界工程師會使用Junit(一個使用Java編寫測試的簡單但又強大的框架)。該框架帶有Test Case類作為所有測試的基類。通過新增適用於環境的方法和工具,所有你的測試用例都可以共享這些共通的基礎設施。
對於任何程式碼都可能有所有測試都需要做的共通的東西。首先針對開發語言選擇一個測試框架。比如,在Java界工程師會使用Junit(一個使用Java編寫測試的簡單但又強大的框架)。該框架帶有Test Case類作為所有測試的基類。通過新增適用於環境的方法和工具,所有你的測試用例都可以共享這些共通的基礎設施。
4 編寫聰敏的測試
測試很耗時間,所以應該確保它們有效。好的測試通過最少可能的程式碼檢測每個元件的核心行為。例如,沒有必要為Java Bean setter和getter方法編寫測試,因為這些會通過其它方法測試到。
反之,應該編寫關注系統行為的測試。你沒有必要做到面面俱到。建立那些進入腦海的測試並隨時準備在重新回來時加入更多測試。
測試很耗時間,所以應該確保它們有效。好的測試通過最少可能的程式碼檢測每個元件的核心行為。例如,沒有必要為Java Bean setter和getter方法編寫測試,因為這些會通過其它方法測試到。
反之,應該編寫關注系統行為的測試。你沒有必要做到面面俱到。建立那些進入腦海的測試並隨時準備在重新回來時加入更多測試。
5 為每個測試準備乾淨的環境
軟體工程師一直都關注效率,所以當他們聽說需要給每個測試單獨準備環境時他們很擔心效能。但是正確地從頭開始準備每個測試很重要。你最不想要的就是因為適用了其他測試的遺留資料而導致的測試失敗。確保每個測試都合理設定並不要去擔心效率。
當你有了一個所有測試的共通環境時你可以在測試基類中加入靜態的設定程式碼塊(static set up block)。
軟體工程師一直都關注效率,所以當他們聽說需要給每個測試單獨準備環境時他們很擔心效能。但是正確地從頭開始準備每個測試很重要。你最不想要的就是因為適用了其他測試的遺留資料而導致的測試失敗。確保每個測試都合理設定並不要去擔心效率。
當你有了一個所有測試的共通環境時你可以在測試基類中加入靜態的設定程式碼塊(static set up block)。
6 適用Mock物件進行有效測試
設定測試不是那麼簡單,而且有時候第一眼的感覺是不可能做到。例如,如果在程式碼中適用Amazon Web服務的程式碼,你如何在不影響實際系統的情況下在測試中模擬它呢?
有幾種方法。你可以製造一些假資料並在測試中適用它們。如果系統有使用者,就可以利用特殊的專門為測試設定的帳戶集合。
針對上線系統執行測試是很危險的,假使某些東西出錯或者你刪掉了實際使用者資料。另一種選擇就是使用稱之為Stub或Mock物件的假資料。
一個Mock物件實現了特定介面並返回預先確定的結果。例如,你可以為Amazon S3建立一個從本地硬碟讀取檔案的Mock物件。Mock物件在測試帶有很多元件的複雜系統是很有用。對於Java,有一些框架可以幫助生成Mock物件,其中最有名是JMock。
設定測試不是那麼簡單,而且有時候第一眼的感覺是不可能做到。例如,如果在程式碼中適用Amazon Web服務的程式碼,你如何在不影響實際系統的情況下在測試中模擬它呢?
有幾種方法。你可以製造一些假資料並在測試中適用它們。如果系統有使用者,就可以利用特殊的專門為測試設定的帳戶集合。
針對上線系統執行測試是很危險的,假使某些東西出錯或者你刪掉了實際使用者資料。另一種選擇就是使用稱之為Stub或Mock物件的假資料。
一個Mock物件實現了特定介面並返回預先確定的結果。例如,你可以為Amazon S3建立一個從本地硬碟讀取檔案的Mock物件。Mock物件在測試帶有很多元件的複雜系統是很有用。對於Java,有一些框架可以幫助生成Mock物件,其中最有名是JMock。
7 在重構程式碼時重構測試
測試只有在你真正投入時才會給你回報。你不僅需要編寫測試,你還需要確保它們是最新的。在你給一個元件加入一個新的方法時,你需要加入一個或多個相應的測試。正像你需要清理不用的程式碼。你也需要清理不再有用的測試。
單體測試在進行大規模重構的時候尤為重要。重構關注程式碼的持續修整以保證其正確。如果你移動程式碼並修改了測試,通過重新執行所有相關的測試就可以確保你沒有在更改系統的過程中破壞任何東西。
測試只有在你真正投入時才會給你回報。你不僅需要編寫測試,你還需要確保它們是最新的。在你給一個元件加入一個新的方法時,你需要加入一個或多個相應的測試。正像你需要清理不用的程式碼。你也需要清理不再有用的測試。
單體測試在進行大規模重構的時候尤為重要。重構關注程式碼的持續修整以保證其正確。如果你移動程式碼並修改了測試,通過重新執行所有相關的測試就可以確保你沒有在更改系統的過程中破壞任何東西。
8在修改缺陷前先寫測試
單體測試是尋找缺陷的有效武器。當你發現你程式碼中的一個問題,在修改程式碼之前寫一個可以暴露該問題的測試。這樣如果同樣的問題重新出現,測試就可以捕捉到。
這點在你無法一次寫好面面俱到的測試的情況下非常重要。在你為一個缺陷加一個測試的時候,你也在以一種訓練有素的方式填補原有測試的漏洞。
單體測試是尋找缺陷的有效武器。當你發現你程式碼中的一個問題,在修改程式碼之前寫一個可以暴露該問題的測試。這樣如果同樣的問題重新出現,測試就可以捕捉到。
這點在你無法一次寫好面面俱到的測試的情況下非常重要。在你為一個缺陷加一個測試的時候,你也在以一種訓練有素的方式填補原有測試的漏洞。
9 利用單體測試確保效能
除了確保程式碼的正確性之外,單體測試還可以確保你的程式碼效能不會隨著時間變遷而退化。很多系統會隨著變大而變慢。
為了編寫效能測試,你需要在你的test class的基類中實現開始和結束函式。在適當的地方你可以使用時間相關的方法或程式碼並斷言消耗的時間在期待效能的極限值之內。
除了確保程式碼的正確性之外,單體測試還可以確保你的程式碼效能不會隨著時間變遷而退化。很多系統會隨著變大而變慢。
為了編寫效能測試,你需要在你的test class的基類中實現開始和結束函式。在適當的地方你可以使用時間相關的方法或程式碼並斷言消耗的時間在期待效能的極限值之內。
10 為併發程式碼編寫測試
併發程式碼很複雜並且通常是很多缺陷的根源。因此對併發程式碼進行單體測試很重要。進行的方法是通過使用休眠和鎖系統(System of Sleeps and Locks)。在測試中你可以在需要等待特定系統狀態的時候呼叫休眠程式碼。儘管這不是100%正確的解決,但在多數情況下這就足夠了。為了在更復雜的環境中模擬併發,你需要在所測試物件之間傳遞鎖。通過這些,你就可以以順序執行的方式模擬併發系統了。
併發程式碼很複雜並且通常是很多缺陷的根源。因此對併發程式碼進行單體測試很重要。進行的方法是通過使用休眠和鎖系統(System of Sleeps and Locks)。在測試中你可以在需要等待特定系統狀態的時候呼叫休眠程式碼。儘管這不是100%正確的解決,但在多數情況下這就足夠了。為了在更復雜的環境中模擬併發,你需要在所測試物件之間傳遞鎖。通過這些,你就可以以順序執行的方式模擬併發系統了。
11 持續執行測試
測試的意義就在於可以不斷執行它。特別對於數十人在同一個程式碼庫上進行開發的團隊中,持續的單體測試尤為重要。你可以將測試設定為每幾個小時執行一次或者在每次程式碼簽入時執行或者每天執行一次(通常在深夜)。可以根據專案確定一個最合適的方法並讓測試可以自動持續執行。
測試的意義就在於可以不斷執行它。特別對於數十人在同一個程式碼庫上進行開發的團隊中,持續的單體測試尤為重要。你可以將測試設定為每幾個小時執行一次或者在每次程式碼簽入時執行或者每天執行一次(通常在深夜)。可以根據專案確定一個最合適的方法並讓測試可以自動持續執行。
12 享受測試!
可能最重要的建議就是享受這一切!當我第一次遇見單體測試時,我有過懷疑並認為這僅僅是增加工作量。但是我給了它一次機會,因為我信任的那個聰明的傢伙告訴我它很有用。
單體測試讓你的大腦進入了一個和編碼狀態很不同的狀態。想著對於特定的元件什麼才是簡單和正確的測試集很有挑戰性。
一旦你開始寫測試,你就會想沒有它們你會怎樣?為了讓測試更有趣你可以加入結對程式設計(Pair Programming)。無論你是和同事一切寫測試程式碼還是幫對方寫測試程式碼,都一定會很有趣。每天結束的時候,看著所有的測試都通過你就知道你的系統真的可以工作了,你會覺得很輕鬆。
可能最重要的建議就是享受這一切!當我第一次遇見單體測試時,我有過懷疑並認為這僅僅是增加工作量。但是我給了它一次機會,因為我信任的那個聰明的傢伙告訴我它很有用。
單體測試讓你的大腦進入了一個和編碼狀態很不同的狀態。想著對於特定的元件什麼才是簡單和正確的測試集很有挑戰性。
一旦你開始寫測試,你就會想沒有它們你會怎樣?為了讓測試更有趣你可以加入結對程式設計(Pair Programming)。無論你是和同事一切寫測試程式碼還是幫對方寫測試程式碼,都一定會很有趣。每天結束的時候,看著所有的測試都通過你就知道你的系統真的可以工作了,你會覺得很輕鬆。
請加入這個討論!和我們一起分享來自你的專案的單體測試經驗。