單元測試準則

來源:互聯網
上載者:User

譯文出處:http://yangyubo.com/unit-testing-guidelines/
原文出處:http://geosoft.no/development/unittesting.html
譯者 (yospaly) 前言:
實施單元測試的時候, 如果沒有一份經過實踐證明的詳細規範, 很難掌握測試的 “度”, 範圍太小施展不開, 太大又侵犯 “別人的” 地盤. 上帝的歸上帝, 凱撒的歸凱撒, 給單元測試念念緊箍咒不見得是件壞事, 反而更有利於發揮單元測試的威力, 為代碼重構和提高代碼品質提供動力.
這份文檔來自 Geotechnical, 是一份非常難得的經驗準則. 你完全可以以這份準則作為模板, 結合所在團隊的經驗, 整理出一份內部單元測試準則.

測試準則:
1. 保持單元測試小巧, 快速
2. 單元測試應該是全自動/非互動
3. 讓單元測試很容易跑起來
4. 對測試進行評估
5. 立即修正失敗的測試
6. 把測試維持在單元層級
7. 由簡入繁
8. 保持測試的獨立性
9. Keep tests close to the class being tested
10. 合理的命名測試案例
11. 只測試公有介面
12. 看成是黑盒
13. 看成是白盒
14. 芝麻函數也要測試
15. 先關注執行覆蓋率
16. 覆蓋邊界值
17. 提供一個隨機值產生器
18. 每個特性只測一次
19. 使用顯式斷言
20. 提供反向測試
21. 代碼設計時謹記測試
22. 不要訪問預定的外部資源
23. 權衡測試成本
24. 合理安排測試優先次序
25. 為測試失敗做好準備
26. 寫測試案例重現 bug
27. 瞭解局限

1. 保持單元測試小巧, 快速
理論上, 任何代碼 Check-in 之前都應該把所有測試套件完整的跑一遍. 所以保持測試代碼輕快能減少開發迭代周期.
2. 單元測試應該是全自動/非互動
測試套件通常是定期執行的, 執行過程也必須是完全自動化才有意義. 輸出結果需要人工檢查的測試不是一個好的單元測試.
3. 讓單元測試很容易跑起來
對開發環境進行配置, 最好是敲一條命令或是點擊一個按鈕就能把單個測試案例和測試套件跑起來.
4. 對測試進行評估
對執行的測試進行覆蓋率分析, 以便得到精確的代碼執行覆蓋率, 調查哪些代碼未被執行.
5. 立即修正失敗的測試
每個開發人員都應該保證新 Check-in 的測試案例能夠跑成功, 並且當有代碼 Check-in 現有測試案例也都能跑通過.
6. 把測試維持在單元層級
單元測試即類 (Class) 的測試. 一個 “測試類別” 應該只對應於一個 “被測類”, 並且對 “被測類” 行為的測試環境應該是隔離的. 必須謹慎的避免使用單元測試架構來測試整個程式的工作流程, 這樣的測試即低效又難維護. 工作流程測試 (譯註: 指跨模組/類的資料流測試) 有它自己的地盤, 但它絕不是單元測試, 必須單獨設定和執行.
7. 由簡入繁
再簡單的測試也遠遠勝過完全沒有測試. 一個簡單的 “測試類別” 會促使建立 “被測類” 基本的測試骨架, 可以對構建環境, 單元測試環境, 執行環境以及覆蓋率分析工具等有效性進行檢查, 同時也確保 “被測類” 能夠整合并被調用.
下面便是單元測試版的 Hello, world! :
void testDefaultConstruction()
{
Foo foo = new Foo();
assertNotNull(foo);
}
8. 保持測試的獨立性
為了保證測試穩定可靠且便於維護, 測試案例之間決不能有相互依賴, 也不能依賴執行的先後次序.
9. Keep tests close to the class being tested
[譯註: 我未翻譯該規則, 個人認為本條規則值得商榷, 大部分 C++ 和 Python 庫均把測試代碼從功能代碼目錄中獨立出來, 通常是建立一個和 src 目錄同級的 tests 目錄, 被測模組/類名之前也常常 不加 Test 首碼. 這麼做保證功能代碼和測試代碼隔離, 目錄結構清晰, 並且發布源碼的時候更容易排除測試案例.]
If the class to test is Foo the test class should be called FooTest (not TestFoo) and kept in the same package (directory) as Foo. Keeping test classes in separate directory trees makes them harder to access and maintain.
Make sure the build environment is configured so that the test classes doesn’t make its way into production libraries or executables.
10. 合理的命名測試案例
確保每個測試方法只測試 “被測類” 的一個明確特性, 並且相應的給測試方法命名. 典型的命名俗定是 test[what], 比如 testSaveAs(), testAddListener(), testDeleteProperty() 等.
11. 只測試公有介面
單元測試可以被定義為 通過類的公有 API 對類進行進行測試. 一些測試載入器允許測試一個類的私人成員, 但這種做法應該避免, 它讓測試變得繁瑣而且更難維護. 如果有私人成員確實需要進行直接測試, 可以考慮把它重構到工具類的公有方法中. 但要注意這麼做是為了改善設計, 而不是協助測試.
12. 看成是黑盒
從在第三方使用者的角度, 測試類別是否滿足規定的需求. 並設法讓它出問題 (譯註: 原文 tear it apart, 本意 “將它撕碎”, 我的理解是崩潰, 出問題, 不能正確工作).
13. 看成是白盒
畢竟被測試類別是程式員自寫自測的, 應該在最複雜的邏輯部分多花些精力測試.
14. 芝麻函數也要測試
通常建議所有重要的函數都應該被測試到, 一些芝麻方法, 如簡單的 setter 和 getter 都可以忽略. 但是仍然有充分的理由支援測試芝麻函數:
• 芝麻 很難定義. 對於不同的人有不同的理解.
• 從黑箱測試的觀點看, 是無法知道哪些代碼是普通的.
• 即便是再芝麻的函數, 也可能包含錯誤, 通常是 “複製粘貼” 代碼的後果:
private double weight_;
private double x_, y_;

public void setWeight(int weight)
{
weight = weight_; // error
}
public double getX()
{
return x_;
}
public double getY()
{
return x_; // error
}
因此建議測試所有方法. 畢竟芝麻函數也容易測試.
15. 先關注執行覆蓋率
區別對待 執行覆蓋率 和 實際測試覆蓋率. 測試的最初目標應該是確保較高的執行覆蓋率. 這樣能保證代碼在 某些 參數輸入時能有效執行. 一旦執行覆蓋率就緒, 就應該開始改進測試覆蓋率了. 注意, 實際的測試覆蓋率很難衡量 (而且往往趨近於 0%).
思考以下公有方法:
void setLength(double length);
調用 setLength(1.0) 你可能會得到 100% 的執行覆蓋率. 要達到 100% 的實際測試覆蓋率, 有多少個 double 浮點數這個方法就必須被調用多少次, 並且要一一驗證行為的正確性. 這無疑是不可能的任務.
16. 覆蓋邊界值
確保參數邊界值均被覆蓋. 對於數字, 測試負數, 0, 正數, 最小值, 最大值, NaN (非數字), 無窮大等. 對於字串, 測試Null 字元串, 單字元, 非 ASCII 字串, 多位元組字串等. 對於集合類型, 測試空, 1, 第一個, 最後一個等. 對於日期, 測試 1月1號, 2月29號, 12月31號等. 被測試的類本身也會暗示一些特定情況下的邊界值. 基本要點是儘可能徹底的測試這些邊界值, 因為它們都是主要 “疑犯”.
17. 提供一個隨機值產生器
當邊界值都覆蓋了, 另一個能進一步改善測試覆蓋率的簡單方法就是產生隨機參數, 這樣每次執行測試都會有不同的輸入.
想要做到這點, 需要提供一個用來產生基本類型 (如: 浮點數, 整型, 字串, 日期等) 隨機值的工具類. 產生器應該覆蓋各種類型的所有取值範圍.
如果測試時間比較短, 可以考慮再裹上一層迴圈, 覆蓋儘可能多的輸入組合. 下面的例子是驗證兩次轉換 little endian 和 big endian 位元組序後是否返回原值. 由於測試過程很快, 可以讓它跑上個一百萬次.
void testByteSwapper()
{
for (int i = 0; i < 1000000; i++) {
double v0 = Random.getDouble();
double v1 = ByteSwapper.swap(v0);
double v2 = ByteSwapper.swap(v1);
assertEquals(v0, v2);
}
}
18. 每個特性只測一次
在測試模式下, 有時會情不自禁的濫用斷言. 這種做法會導致維護更困難, 需要極力避免. 僅對測試方法名指示的特性進行明確測試.
因為對於一般性代碼而言, 保證測試代碼儘可能少是一個重要目標.
19. 使用顯式斷言
應該總是優先使用 assertEquals(a, b) 而不是 assertTrue(a == b), 因為前者會給出為何導致測試失敗的更有意義的資訊. 在事先不確定輸入值的情況下, 這條規則尤為重要, 比如之前使用隨機參數值組合的例子.
20. 提供反向測試
反向測試是指刻意編寫問題代碼, 來驗證魯棒性和能否正確的處理錯誤.
假設如下方法的參數如果傳進去的是負數, 會立馬拋出異常:
void setLength(double length) throws IllegalArgumentExcepti
可以用下面的方法來測試這個特例是否被正確處理:
try {
setLength(-1.0);
fail(); // If we get here, something went wrong
}
catch (IllegalArgumentException exception) {
// If we get here, all is fine
}
21. 代碼設計時謹記測試
編寫和維護單元測試的代價是很高的, 減少代碼中的公有介面和迴圈複雜度是降低成本, 使高覆蓋率測試代碼更易於編寫和維護的有效方法.
一些建議:
• 使類成員常量化, 在建構函式中進行初始化. 減少 setter 方法的數量.
• 限制過度使用繼承和公有虛函數.
• 通過使用友元類 (C++) 或包範圍 (Java) 來減少公有介面.
• 避免不必要的邏輯分支.
• 在邏輯分支中編寫儘可能少的代碼.
• 在公有和私人介面中盡量多用異常和斷言驗證參數參數的有效性.
• 限制使用快捷函數. 對於黑箱而言, 所有方法都必須一視同仁的進行測試. 考慮以下簡短的例子:
public void scale(double x0, double y0, double scaleFactor)
{
// scaling logic
}
public void scale(double x0, double y0)
{
scale(x0, y0, 1.0);
}
刪除後者可以簡化測試, 但使用者代碼的工作量也將略微增加.
22. 不要訪問預定的外部資源
單元測試代碼不應該假定外部的執行環境, 以便在任何時候/任何地方都能執行. 為了向測試提供必需的資源, 這些資源應該由測試本身提供.
比如一個解析某類型檔案的類, 可以把檔案內容嵌入到測試代碼裡, 在測試的時候寫入到臨時檔案, 測試結束再刪除, 而不是從預定的地址直接讀取.
23. 權衡測試成本
不寫單元測試的代價很高, 但是寫單元測試的代價同樣很高. 要在這兩者之間做適當的權衡, 如果用執行覆蓋率來衡量, 業界標準通常在 80% 左右.
很典型的, 讀寫外部資源的錯誤處理和異常處理就很難達到百分百的執行覆蓋率. 類比資料庫在交易處理到一半時發生故障並不是辦不到, 但相對於進行大範圍的代碼審查, 代價可能太大了.
24. 合理安排測試優先次序
單元測試是典型的自底向上過程, 如果沒有足夠的資源測試一個系統的所有模組, 就應該先把重點放在較底層的模組.
25. 為測試失敗做好準備
考慮下面的這個例子:
Handle handle = manager.getHandle();
assertNotNull(handle);

String handleName = handle.getName();
assertEquals(handleName, "handle-01");
如果第一個宣告失敗, 緊接其後的語句會導致代碼崩潰, 剩下的測試都將不被執行. 任何時候都要為測試失敗做好準備, 避免單個失敗的測試項中斷整個測試套件的執行. 上面的例子可以重寫成:
Handle handle = manager.getHandle();
assertNotNull(handle);
if (handle == null) return;

String handleName = handle.getName();
assertEquals(handleName, "handle-01");
26. 寫測試案例重現 bug
每上報一個 bug, 都要寫一個測試案例來重現這個 bug (即無法通過測試), 並用它作為成功修正代碼的標準.
27. 瞭解局限
單元測試永遠無法證明代碼的正確性
一個跑失敗的測試可能表明代碼有錯誤, 但一個跑成功的測試什麼也證明不了.
單元測試最有效應用場合是驗證和, 以及 迴歸測試: 當新功能增加和代碼進行重構的同時,會不會影響到舊功能的正確性

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.