單元測試

針對程序模塊進行正確性檢驗的測試工作

電腦編程中,單元測試(英語:Unit Testing)又稱為模組測試 [來源請求] ,是針對程式模組軟體設計的最小單位)來進行正確性檢驗的測試工作。程式單元是應用的最小可測試部件。在程序化編程中,一個單元就是單個程式、函式、過程等;對於物件導向程式設計,最小單元就是方法,包括基礎類別(超類)、抽象類、或者衍生類別(子類)中的方法。

通常來說,程式設計師每修改一次程式就會進行最少一次單元測試,在編寫程式的過程中前後很可能要進行多次單元測試,以證實程式達到軟體規格書要求的工作目標,沒有程式錯誤;雖然單元測試不是必須的,但也不壞,這牽涉到專案管理的政策決定。

每個理想的測試案例獨立於其它案例;為測試時隔離模組,經常使用stubs、mock[1]或fake等測試馬甲程式。單元測試通常由軟體開發人員編寫,用於確保他們所寫的代碼符合軟體需求和遵循開發目標。它的實施方式可以是非常手動的(透過紙筆),或者是做成構建自動化的一部分。

收益

單元測試的目標是隔離程式部件並證明這些單個部件是正確的。[2]一個單元測試提供了代碼片斷需要滿足的嚴密的書面規約。因此,單元測試帶來了一些益處。 單元測試在軟體開發過程的早期就能發現問題。

適應變更

單元測試允許程式設計師在未來重構代碼,並且確保模組依然工作正確(複合測試)。這個過程就是為所有函式方法編寫單元測試,一旦變更導致錯誤發生,藉助於單元測試可以快速定位並修復錯誤。

可讀性強的單元測試可以使程式設計師方便地檢查代碼片斷是否依然正常工作。良好設計的單元測試案例覆蓋程式單元分支和迴圈條件的所有路徑。

在連續的單元測試環境,通過其原生的持續維護工作,單元測試可以延續用於準確反映當任何變更發生時可執行程式和代碼的表現。藉助於上述開發實踐和單元測試的覆蓋,可以分分秒秒維持準確性。

簡化整合

單元測試消除程式單元的不可靠,採用由下而上的測試路徑。通過先測試程式部件再測試部件組裝,使整合測試變得更加簡單。

業界對於人工整合測試的必要性存在較大爭議。儘管精心設計的單元測試體系看上去實現了整合測試,因為整合測試需要人為評估一些人為因素才能證實的方面,單元測試替代整合測試不可信。一些人認為在足夠的自動化測試系統的條件下,人力整合測試組不再是必需的。事實上,真實的需求最終取決於開發產品的特點和使用目標。另外,人工或手動測試很大程度上依賴於組織的可用資源。[來源請求]

文件記錄

單元測試提供了系統的一種文件記錄。藉助於檢視單元測試提供的功能和單元測試中如何使用程式單元,開發人員可以直觀的理解程式單元的基礎API。

單元測試具體表現了程式單元成功的關鍵特點。這些特點可以指出正確使用和非正確使用程式單元,也能指出需要擷取的程式單元的負面表現(譯註:異常和錯誤)。儘管很多軟體開發環境不僅依賴於代碼做為產品文件,在單元測試中和單元測試本身確實文件化了程式單元的上述關鍵特點。

另一方面,傳統文件易受程式本身實現的影響,並且時效性難以保證(如設計變更、功能擴充等在不太嚴格時經常不能保持文件同步更新)。

表達設計

在測試驅動開發的軟體實踐中,單元測試可以取代正式的設計。每一個單元測試案例均可以視為一項類、方法和待觀察行為等設計元素。下面的Java例可以幫助說明這一點。

這是一個證明一批實現設計元素的測試類。首先,要求有一個名為Adder的介面,和一個不帶參數的構造方法名為AdderImpl的實現類。然後,它斷言Adder介面包含有一個兩個整數參數返回值為整型的add方法。它也通過小範圍的值檢驗說明方法的行為。

public class TestAdder {
    public void testSum() {
        Adder adder = new AdderImpl();
        assert(adder.add(1, 1) == 2);
        assert(adder.add(1, 2) == 3);
        assert(adder.add(2, 2) == 4);
        assert(adder.add(0, 0) == 0);
        assert(adder.add(-1, -2) == -3);
        assert(adder.add(-1, 1) == 0);
        assert(adder.add(1234, 988) == 2222);
    }
}

這個案例中,單元測試在程式之前寫成,用作指明待設計的對象形態和行為的文件,沒有任何實現細節,留作程式設計師練習。以下可能是最簡單的工作實踐,這個最容易的解決方案可以通過上述測試:

interface Adder {
    int add(int a, int b);
}
class AdderImpl implements Adder {
    int add(int a, int b) {
        return a + b;
    }
}

不同於其他基於圖的設計方法,用單元測試表達設計有一項顯著優點:設計文件(單元測試本身)可以用於驗證程式實現符合設計。UML可能會遇到這樣的問題:儘管圖上一個類被命名為Customer,但開發人員可以稱其為Wibble,而且系統中沒有任何地方會顯示出這個差異。基於單元測試設計方法,開發人員不遵循設計要求的解決方案永遠不會通過測試。

當然,單元測試缺乏圖的可讀性,但UML圖可以在自由工具(通常可從IDE擴充取得)中為大多數現代程式語言生成UML圖,很難要求採購昂貴的UML設計套裝軟體。自由工具,類似於基於xUnit框架的工具,測試結果輸出到一些可生成供人工識讀的圖形化工具系統中去。

分離介面和實現

因為很多類會參照其它,對這個類的測試經常會要求測試其它的類。一個最普遍的例子是依賴於資料庫的類:為了測試它,測試人員通常編寫代碼去運算元據庫。這是不對的,因為單元測試不應超出待測試的類邊界。

作為替代,軟體開發人員應建立一個資料庫連接的抽象介面,然後實現這個介面的類比對象。通過對代碼所需附件的抽象(臨時降低了網狀的耦合效應),這些獨立程式單元較前者更能被完整測試。高品質的代碼單元也可提供更好的可維護性。

局限

測試不可能發現所有的程式錯誤,單元測試也不例外。按定義,單元測試只測試程式單元自身的功能。因此,它不能發現整合錯誤、效能問題、或者其他系統級別的問題。單元測試結合其他軟體測試活動更為有效。與其它形式的軟體測試類似,單元測試只能表明測到的問題,不能表明不存在未測試到的錯誤。

軟體測試是一個組合問題。例如,每一個布林型的決斷語句需要至少兩種測試:一個返回真,一個返回假。因此,針對每行書寫的代碼,程式設計師通常需要寫3至5行的測試代碼。[3]這很明顯地很花時間而且對此的投入可能並不值得。也有些問題是根本不能簡單地檢測出來的——例如具不確定性的或牽扯到多執行緒的問題。此外,替單元測試寫的程式碼可能就像要測試的程式碼一樣有程式錯誤。佛瑞德·布魯克斯人月神話一書中舉例說明:「絕對不要帶兩個計時器去海邊。最好總是帶一或三個」。意味著,如果兩個計時器互相衝突的話,你該怎麼知道哪個是對的?為了獲得單元測試的好處,在軟體開發過程中應形成一套嚴格紀律意識。仔細保留記錄是必要的,不僅僅只保留執行的測試,也包括保留對應的原始碼和其它軟體單元的變更歷史。即,使用版本控制系統是必要的。如果後續版本不能通過一個以前測試通過的單元測試,版本控制系統可以提供對應時間段對原始碼所做的變更清單。

每天養成檢視單元測試案例失敗測試並及時確定錯誤原因的習慣是必要的。[4]如果沒有這樣的流程,沒有在團隊工作流程中體現,單元測試系列將走向不同步,造成越來越多的錯誤和越來越低效的單元測試案例系列。

應用

極限編程

單元測試是極限編程的基礎,依賴於自動化的單元測試框架。自動化的單元測試框架可以來源於第三方,如xUnit,也可以由開發組自己建立。

極限編程建立單元測試用於測試驅動開發。首先,開發人員編寫單元測試用於展示軟體需求或者軟體缺陷。因為需求尚未實現或者現有代碼中存在軟體缺陷,這些測試會失敗。然後,開發人員遵循測試要求編寫最簡單的代碼去滿足它,直到測試得以通過。

系統中大多數代碼都經過單元測試,但並非所有代碼路徑都必需單元測試。極限編程強調「測試所有可能中斷」的策略,而傳統方法是「測試所有執行路徑」。這使得極限編程開發人員比傳統開發少寫單元測試,但這並不是問題。不爭的事實是傳統方法很少完全遵循完整地測試所有執行路徑的要求。[來源請求]極限編程相互地認識到測試很少能完備(因為完備測試通常需要昂貴的代價和時間消耗,意味著不經濟),提供了如何有效地將有限資源集中投入可花費的代價到問題關鍵的導引。

至關重要的,測試代碼應視為第一個專案成品,與實現代碼維持同等級別的品質要求,沒有重複。開發人員在提交程式單元代碼時一併提交單元測試代碼到代碼庫。徹底的極限編程單元測試代碼提供上述單元測試的收益,如簡化和更可信的程式開發和重構、簡化代碼整合、精確的文件和模組化的設計。而且,單元測試經常作為複合測試的一種形式被執行。

技術

單元測試通常情況下自動進行,但也可被手動執行。IEEE沒有偏愛某一種形式。[5]手動的單元測試可用於step-by-step的教學文件。儘管如此,單元測試的目標是隔離程式單元並驗證其正確性。自動執行使目標達成更有效,也可獲得本文上述單元測試收益。相反,不細心規劃或者精心的單元測試可能被視為包括多個軟體組件的整合測試案例,於是將因未完全達到建立單元測試的預定目標,測試可能失去較多收益。

在自動化測試時,為了實現隔離的效果,測試將脫離待測程式單元(或代碼主體)本身原生的執行環境之外,即脫離產品環境或其本身被建立和呼叫的上下文環境,而在測試框架中執行。以隔離方式執行有利於充分顯露待測試代碼與其它程式單元或者產品資料空間的依賴關係。這些依賴關係在單元測試中可以被消除。

藉助於自動化測試框架,開發人員可以抓住關鍵進行編碼並通過測試去驗證程式單元的正確性。在測試案例執行期間,框架通過紀錄檔記錄了所有失敗的測試準則。很多測試框架可以自動標記和提交失敗的測試案例總結報告。根據失敗的程度不同,框架可以中止後續測試。

總體說來,單元測試會激發程式設計師創造解耦的和內聚的代碼體。單元測試實踐有利於促進健康的軟體開發習慣。設計模式、單元測試和重構經常一起出現在工作中,藉助於它們,開發人員可以生產出最為完美的解決方案。

單元測試框架

單元測試框架通常是沒有作為編譯器套件的第三方產品。他們幫助簡化單元測試的過程,並且已經為各種程式語言開發。

通常在沒有特定框架支援下,透過撰寫在測試中的執行單元,並使用判定異常處理、或其他控制流程機制來表示失敗的使用者代碼(client code)執行單元測試是可行的。不透過框架的單元測試有用之處在於進行單元測試時會有一個入行障礙英語Barriers to entry;進行一點單元測試幾乎不比沒做好多少,但是一旦使用了框架,加入單元測試相對來說會簡單許多。[6]在某些框架中許多先進單元測試特徵遺失了或者必須是手工編寫的。

語言層單元測試支援

某些程式語言直接支援單元測試。他們的語法允許直接進行單元測試的宣告而不需要匯入(不管是第三方的或標準的)。除此之外,單元測試的布林條件可以用與非單元測試碼的布林表示法相同的語法來表示,例如ifwhile宣告的用法。

直接支援單元測試的語言套件含了:

參見

參考文獻

  1. ^ Fowler, Martin. Mocks aren't Stubs. 2007-01-02 [2008-04-01]. (原始內容存檔於2008-03-19). 
  2. ^ Kolawa, Adam; Huizinga, Dorota. Automated Defect Prevention: Best Practices in Software Management. Wiley-IEEE Computer Society Press. 2007: 75/426 [2010-07-22]. ISBN 0470042125. (原始內容存檔於2012-04-25). 
  3. ^ Cramblitt, Bob. Alberto Savoia sings the praises of software testing. 2007-09-20 [2007-11-29]. (原始內容存檔於2013-07-16). 
  4. ^ daVeiga, Nada. Change Code Without Fear: Utilize a regression safety net. 2008-02-06 [2008-02-08]. (原始內容存檔於2009-05-20). 
  5. ^ IEEE Standards Board, "IEEE Standard for Software Unit Testing: An American National Standard, ANSI/IEEE Std 1008-1987"頁面存檔備份,存於網際網路檔案館) in IEEE Standards: Software Engineering, Volume Two: Process Standards; 1999 Edition; published by The Institute of Electrical and Electronics Engineers, Inc. Software Engineering Technical Committee of the IEEE Computer Society.
  6. ^ Bullseye Testing Technology. "Intermediate Coverage Goals". 2006–2008 [24 March 2009]. (原始內容存檔於2009-12-27). 

外部連結