[C#][Unit Test] 02. 如何寫一個好的單元測試
本文利用C#與Visual Studio平台,來探討時下最夯的Unit Test。
此系列文轉自我們的好朋友Toyo大大網誌(原文連結),在Windows業界耕耘了多年,累積了不少實戰經驗。此為他專門設計為新人的單元測試教學,進度條獲授權轉載。
原文標題: 【Unit Test】Day 2 - 如何寫一個好的單元測試
接著來討論一下單元測試究竟該怎麼寫,
你可能也會有這樣的疑問,如果今天我的程式是要呼叫資料庫來取得資料,那是否真要接上一個資料庫去做單元測試呢?
又公司的資料庫如果對外是連不到的,悲苦的工程師回家怎麼做單元測試呢?那如果是外部API呢?網路不通時單元測試是不是就都壞掉了?
該怎麼知道現在單元測試是真正的邏輯錯誤還是只是因為網路或是資料庫連不上導致的錯誤?是不是寫了單元測試後反而我要花大量的時間常常在檢查到底現在是錯哪邊啊...
其實我剛開始接觸單元測試的時候上面的問題也都想過一輪,但這也點出了單元測試一個很重要的特性
看完這句話你可能會默默響起OS,「阿鬼,你還是說中文吧~」。
不過這段容我之後另外篇幅再做詳盡的解說,現在只要有這樣的觀念就好,單元測試不應該隨著你在的環境不同而有結果的落差,他關注的是邏輯而不是與外部的關聯。
那好,假設我們做到了單元測試跟外部的關聯都斷開了,只專注在自己的邏輯上,這樣就稱得上是好的單元測試了嗎? 當然不是! 單元測試的命名也是一個很重要的課題。
還記得上一篇文章提到,撰寫良好的單元測試應該像是規格書一般,不僅要讓專案品質提高,更是要充當新接觸專案人員能透過閱讀單元測試對程式有基本認識的工具。
舉例來說:有一個單元測試標題這樣寫「public void GetTest_呼叫得到True()」,對於一個不看單元測試內容的人來說,這個標題一點意義都沒有,首先他不知道這個方法裡面是在幹嘛的,再來他究竟會做什麼事情導致他得到True,帶入的參數意義是什麼...等,這樣的單元測試反而是造成專案難以維護的幫兇。
所以比較好的單元測試標題應該詳盡,例:「public void GetTest_帶入會員ID_應回該ID搜尋到的會員資料DTO」,盡量符合:
比起第一種命名方式是否明確易懂多了。
你可能又會冒出一個問題,如果這個方法帶進的ID查不到會員時,我會回傳Null,但怎麼在一個單元測試表示?這邊點出另一個重點:
順著上面的邏輯,這個方法應該就會有另一個測試為「public void GetTest_帶入會員ID_如搜尋不到該ID的會員_回傳Null」。
所以如果一個方法裡面的IF ELSE很多,導致程式邏輯複雜度提高,則單元測試可能就會有對應的很多方法來涵蓋所有可能。
所以如果有寫單元測試,則職責分離就是一個重要的課題,如果你把很多職責都放在同一段程式中,你的單元測試可能是倍數成長之外,測試也會變得很難撰寫。
從上面的舉例也可以看出,每個單元測試都只關注一種邏輯,一個方法難免包含多種邏輯狀況。但當修改程式時如果單元測試錯誤,也能幫助你快速鎖定可能是哪一段邏輯錯了,減少除錯的時間。
再者,因為每個單元測試名稱都很明確,執行的方法帶入的參數也都明確的情況下,讓人閱讀時可以很容易進入狀況,符合一開始提到的可以執行的規格書,對專案是非常有幫助的。
接著來探討單元測試的內文該如何撰寫,首先應該符合所謂的3A原則
Act = 執行受測方法
Assert = 驗證執行結果與預測結果是否一致
拿昨天的單元測試來說明
Arrange中Sut(System Under Test ),受測的目標為EasyMethod這跟Class,而這個單元測試預期的結果為7。
Act中actual是EasyMethod執行Method1這個方法得到的結果。
Assert中用MsTest提供的Assert.AreEqual方法驗證得到的結果與預期的結果是否一致。
如果每個單元測試都照著這樣的風格撰寫,則對於閱讀的人來說會很清楚歸類出每個區段各自的職責。
那今天就談到這邊,之後應該就會有比較多的實作了(擦汗)
上一篇:01. 為何要寫單元測試
最後,如果你喜歡我們的文章,別忘了到我們的FB粉絲團按讚喔!!