摘要:單元測試曾一直都是阿里的痛,在進入阿里的這段日子里,單元測試逐步成熟起來,也開始帶來了一些收益,這些方面都是很不錯的一些思路。后面我打算仔細的研究和整理這一過程,希望能提供一個比較可行的方案供大家參考。
網上一直流傳的一份挺不錯的單元測試準則,先保存下來。
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 (非數字), 無窮大等. 對于字符串, 測試空字符串, 單字符, 非 ASCII 字符串, 多字節字符串等. 對于集合類型, 測試空, 1, 第一個, 最后一個等. 對于日期, 測試 1月1號, 2月29號, 12月31號等. 被測試的類本身也會暗示一些特定情況下的邊界值. 基本要點是盡可能徹底的測試這些邊界值, 因為它們都是主要 “疑犯”.
原文轉自:http://www.wangyuxiong.com/archives/51625