細數35個單元測試準則

1.保持單元測試小巧、快速

理論上,任何代碼提交前都應該完整跑一遍全部測試套件。保持測試代碼執行符合預期,這樣可以縮短迭代開發週期。程序員

2.單元測試應該是全自動/非交互式的

測試套件一般是按期執行的,執行過程必須徹底自動化纔有意義。輸出結果須要人工檢查的測試不是一個好的單元測試。面試

3.讓單元測試很容易跑起來

對開發環境進行配置,最好是敲一條命令或是點擊一個按鈕就能把單個測試用例或測試套件跑起來。數據庫

4.對測試進行評估

對執行的測試進行覆蓋率分析,獲得精確的代碼執行覆蓋率,並調查哪些代碼未被執行。app

5.當即修正失敗的測試

每一個開發人員在提交前都應該保證新的測試用例執行成功,當有代碼提交時,現有測試用例也都能跑通。
若是一個按期執行的測試用例執行失敗,整個團隊應該放下手上的工做先解決這個問題。框架

6.把測試維持在單元級別

單元測試即類(Class)的測試。一個「測試類」應該只對應於一個「被測類」,而且「被測類」的行爲應該被隔離測試。必須謹慎避免使用單元測試框架來測試整個程序的工做流,這樣的測試即低效又難維護。工做流測試(譯註:指跨模塊、類的數據流測試)有它本身的地盤,但它毫不是單元測試,必須單獨創建和執行。dom

7.由簡入繁

再簡單的測試也遠遠賽過徹底沒有測試。一個簡單的「測試類」會促使創建「被測類」基本的測試骨架,能夠對構建環境、單元測試環境、執行環境以及覆蓋率分析工具等有效性進行檢查,同時也能夠證實「被測類」可以被整合和調用。
下面即是單元測試版的「Hello, world!」:函數

void testDefaultConstruction()
{
Foo foo = new Foo();
assertNotNull(foo);
}
8.保持測試的獨立性

爲了保證測試穩定可靠且便於維護,測試用例之間決不能有相互依賴,也不能依賴執行的前後次序。(實際上TestNG有提供依賴的功能,或許某些場景也須要依賴。)工具

9.Keep tests close to the class being tested

[譯註:有意翻譯該規則,我的認爲本條規則值得商榷,大部分C++、Objective-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[MethodName]」,好比「testSaveAs()」、「testAddListener()」、「testDeleteProperty()」等。測試

11.只測公有接口

單元測試能夠被定義爲經過類的公有API對類進行測試。一些測試工具容許測試一個類的私有成員,但這種作法應該避免,它讓測試變得繁瑣並且更難維護。若是有私有成員確實須要進行直接測試,能夠考慮把它重構到工具類的公有方法中。但要注意這麼作是爲了改善設計,而不是幫助測試。

12.當作是黑盒

站在第三方使用者的角度,測試一個類是否知足規定的需求。並設法讓它出問題。

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號、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 IllegalArgumentException
能夠用下面的方法來測試這個特例是否被正確處理:

try {
setLength(-1.0);
fail();  // If we get here, something went wrong
}
catch (IllegalArgumentException exception) {
// If we get here, all is fine
}
21.代碼設計時謹記

編寫和維護單元測試的代價是很高的,減小代碼中的公有接口和循環複雜度是下降成本和使高覆蓋率測試代碼更易於編寫和維護的有效方法。
一些建議:

22.使類成員常量化,在構造函數中進行初始化。減小setter方法的數量。
23.限制過分使用繼承和公有虛函數。
24.經過使用友元類(C++)或包做用域(Java)來減小公有接口。
25.避免沒必要要的邏輯分支。
26.在邏輯分支中編寫儘量少的代碼。
27.在公有和私有接口中儘可能多用異常和斷言驗證參數參數的有效性。
28.限制使用快捷函數。

對於黑箱而言,全部方法都必須一視同仁的進行測試。考慮如下簡短的例子:

public void scale(double x0, double y0, double scaleFactor)
{
// scaling logic
}
public void scale(double x0, double y0)
{
scale(x0, y0, 1.0);
}
29.刪除後者能夠簡化測試,但用戶代碼的工做量也將略微增長。
30.不要訪問預設的外部資源

單元測試代碼不該該假定外部的執行環境,以便在任什麼時候候和任何地方都能執行。爲了向測試提供必需的資源,這些資源應該由測試自己提供。
好比:一個解析某類型文件的類,能夠把文件內容嵌入到測試代碼裏。在測試的時候寫入到臨時文件,測試結束再刪除,而不是從預約的地址直接讀取。

31.權衡測試成本

不寫單元測試的代價很高,可是寫單元測試的代價一樣很高。要在這二者之間作適當的權衡,若是用執行覆蓋率來衡量,業界標準一般在80%左右。
很典型的,讀寫外部資源的錯誤處理和異常處理就很難達到百分百的執行覆蓋率。模擬數據庫在事務處理到一半時發生故障並非辦不到,但相對於進行大範圍的代碼審查,代價可能太大了。

32.安排測試優先次序

單元測試是典型的自底向上過程,若是沒有足夠的資源測試一個系統的全部模塊,就應該先把重點放在較底層的模塊。

33.測試代碼要考慮錯誤處理

考慮下面的這個例子:

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");
34.寫測試用例重現Bug

每上報一個Bug,都要寫一個測試用例來重現這個Bug(即沒法經過測試),並用它做爲成功修正代碼的檢驗標準。

35.瞭解侷限

單元測試永遠沒法證實代碼的正確性!!一個跑失敗的測試可能代表代碼有錯誤,但一個跑成功的測試什麼也證實不了。單元測試最有效的使用場合是在一個較低的層級驗證並文檔化需求,以及迴歸測試:開發或重構代碼,不會破壞已有功能的正確性。以上內容就是本篇的所有內容以上內容但願對你有幫助,有被幫助到的朋友歡迎點贊,評論。若是對軟件測試、接口測試、自動化測試、面試經驗交流。感興趣能夠關注博主,加入咱們會有同行一塊兒技術交流哦。

相關文章
相關標籤/搜索