前言
本 文是閱讀了《單元測試之道》一書後的筆記,也是公司安排本人進行單元測試教育訓練的材料,原文是一個powerpoint,故修改了下,并針對visual studio 2005自帶的單元測試做的一個整理,将其奉獻出來,目的是供需要了解和學習單元測試的朋友們閱讀。如有錯誤望指出。
什麼是單元測試?
單 元測試是開發者編寫的一小段代碼,用于檢驗被測代碼的一個很小的、很明确的功能是否正确。通常而言,一個單元測試是用于判斷某個特定條件(或者場景)下某 個特定函數的行為。例如,你可能把一個很大的值放入一個有序list 中去,然後确認該值出現在list 的尾部。或者,你可能會從字元串中删除比對某種 模式的字元,然後确認字元串确實不再包含這些字元了。
執行單元測試,是為了證明某段代碼的行為确實和開發者所期望的一緻。
為什麼需要單元測試?
當 編寫項目的時刻,如果我們假設底層的代碼是正确無誤的,那麼先是高層代碼中使用了底層代碼;然後這些高層代碼又被更高層的代碼所使用,如此往複。當基本的 底層代碼不再可靠時,那麼必需的改動就無法隻局限在底層。雖然你可以修正底層的問題,但是這些對底層代碼的修改必然會影響到高層代碼。于是,一個對底層代 碼的修正,可能會導緻對幾乎所有代碼的一連串改動,進而使修改越來越多,也越來越複雜。進而使整個項目也以失敗告終。
而單元測試的核心内涵:這個簡單有效的技術就是為了令代碼變得更加完美。
什麼是斷言
assertion(斷言),它是一個簡單的方法調用,用于判斷某個語句是否為真。
例如:
public void istrue(bool condtion){
if(!condition) abort();
}
應用則為:
int a=2;
istrue(a==2);
還可以編寫更多的特定資料類型的斷言。
計劃你的單元測試
當我們編寫了一個如下的函數,它用于查找list中的最大值:static int largest(int[] list);
所能想到的測試如下:
<col>
輸入
預期結果
7,8,9
9
8,9,7
9,7,8
7,9,8,9
1
-9,-8,-7
-7
null
exception
建立單元測試
在解決方案資料總管中右擊某個測試項目,或在 visual studio 代碼編輯器中,右擊要測試的命名空間、類或方法并選擇“建立單元測試”。
vsunit 的各種斷言
在測試方法中,可以調用任意數量的 assert 類方法,如 assert.areequal()。assert 類有很多方法可供選擇,其中許多方法具有若幹重載。
使用 collectionassert 類可比較對象集合,也可驗證一個或多個集合的狀态。
使用 stringassert 類可對字元串進行比較。此類包含各種有用的方法,如 stringassert.contains、stringassert.matches 和 stringassert.startswith。
隻要測試失敗,就會引發 assertfailedexception 異常。如果測試逾時,引發意外的異常,或包含生成了 failed 結果的 assert 語句,則該測試失敗。
隻要測試生成的結果為 inconclusive,就會引發 assertinconclusiveexception。通常,向仍在處理的測試添加 assert.inconclusive 語句可訓示該測試尚未準備好,不能運作。
編寫新的 assert 異常類時使該類從基類 unittestassertexception 進行繼承,可更友善地将異常辨別為斷言失敗而非從測試或産品代碼引發的意外異常。
如果希望開發代碼中的某方法引發異常,又想用測試方法來驗證是否真的在該方法中引發了異常,則請用 expectedexceptionattribute 屬性來修飾測試方法。
如:
[testmethod]
[expectedexception(typeof(argumentexception),
"userid 為 null 的異常檢測.")]
public void nulluseridinconstructor()
{
logoninfo logoninfo = new logoninfo(null, "p@ss0word");
}
單元測試的屬性
除了單元測試方法的 [testmethod()] 屬性及其包容類的 [testclass()] 屬性之外,可使用其他屬性啟用特定的單元測試功能。在這些屬性中,最主要的屬性有 [testinitialize()] 和 [testcleanup()]。使用标記有 [testinitialize()] 的方法對将要在其中運作單元測試的環境的各個方面進行準備;這樣做的目的在于為單元測試的運作建立已知的狀态。例如,可以使用 [testinitialize()] 方法複制、更改或建立測試中将要使用的某些資料檔案。
在運作完某個測試後,可通過标記有 [testcleanup()] 的方法将環境傳回到已知狀态;這可能意味着需要删除檔案夾中的檔案,或将某個資料庫傳回到已知狀态。例如,在測試了訂單錄入應用程式中使用的某個方法後,可将庫存資料庫重置為初始狀态。此外,建議您在 [testcleanup()] 或 classcleanup 方法中使用清除代碼,而不要在終結器方法(~constructor)中使用此代碼。從終結器方法引發的異常不會被捕捉到,并且會導緻無法預料的結果。
用于建立調用順序的屬性
對于程式集:
在加載程式集之後以及解除安裝程式集之前,将調用 assemblyinitialize和 assemblycleanup。
對于類 :
在加載類之後以及解除安裝類之前,将調用 classinitialize 和 classcleanup。
對于測試方法 :
在每個測試方法加載以及解除安裝之前,将調用testinitialize 和testcleanup
測試上下文類的屬性存儲有關目前測試運作的資訊。例如,testcontext.datarow 和 testcontext.dataconnection 屬性包含測試用于資料驅動的單元測試的資訊。
用于對測試進行辨別和排序的屬性
測試配置類
用于生成報告的屬性
用于專用通路器的類
right----------結果是否正确?
b---------------是否所有的邊界條件都是正确的?
i----------------能查一下反向關聯嗎?
c---------------能用其他手段交叉檢查一下結果嗎?
e---------------你是否可以強制錯誤條件發生?
p---------------是否滿足性能要求?
right:結果是否正确
如果代碼能運作正确,如何才知道它是正确的呢?
1、使用更明确的設計文檔
2、真實環境資料
3、????
b:邊界條件
盡可能的至少各種特殊或者意外的情況,測試程式是否能正常工作,如:
l 完全僞造或者不一緻的輸入資料,如叫做“(*@q!&#?±的檔案。
l 格式錯誤的資料,如錯誤格式的郵件位址
l 空值或不完整的值
l 一些與意料中的合理值相去甚遠的值,如年紀為10000
l 如果要求是一個不允許出現重複數值的list,但傳入一個有重複數值的list
l 要求是一個有序的list,但傳入一個無序的list
l 處理的順序是錯誤的,或者與期望的次序不一緻。如未登入系統就嘗試列印。
b:邊界條件的-correct
conformance(一緻性)值是否和預期的一緻
ordering(順序性)值是否應該的那樣有序或者無序
range(區間性)值是否位于合理範圍
reference(依賴性)代碼是否引用了一些代碼本身控制範圍之外的資源
existence(存在性)值是否存在(是否非空,非零,在集合中等等)
cardinality(基數性)是否恰好有足夠的值
time(相對或絕對的時間性)所有事情的發生是否有序?是否在正确的時間?是否恰好及時?
i:檢查反向關聯
通常一些結果可以使用反向的邏輯關系來驗證它們是否正确,如:計算a*b的函數,測試方法如下:
public void usinginverse(){
double x = mymath.ab(4,4);
assert.areequal<double>(x,4*4);
c:使用其他手段實作交叉檢查
計算一個結果可以存在多個算法,同一個算法可以使用穩定的版本來校驗新改進的版本,如:
public void usingstd(){
double number = 23214.01;
double result1 = mymath.舊方法(number1);
double result2 = mymath.新方法(number1);
assert.areequal<double>(result1,result2);
e:強制産生錯誤條件
真實運作環境各種出乎意料的事情都可能發生,如斷電,斷網等等。在測試中模拟這些情況可以使用mock對象來實作。
p:性能特性
如資料采集的功能,在十個網站上進行采集工作很正常,那麼在1000個網站上或更多的網站上進行采集它的速度如何?是否寫個單元測試?
如何才是一個好的單元測試
好的測試應該具有的品質是:a-trip(合稱)
automatic 自動化
thorough 徹底的
repeatable可重複
independent獨立的
professional 專業的
automatic自動化
調用自動化
檢查結果自動化
測試所有可能會出現問題的情況,一個極端是,對于每行代碼、代碼可能到達的分支,每個可能抛出的異常等等,都可以作為測試的對象。另一個極端是,你僅僅測試最可能的情況---邊界條件、殘缺和畸形的資料等等。然而這些都基于項目需求的決策問題。
這些所說的歸納為“代碼覆寫率”。
repeatable 可重複
測試應該獨立于所有其他的測試,而且必須獨立于周圍的環境。目标隻有一個,就是測試應該能夠以任意的順序一次又一次的運作,并且産生相同的結果。如果結果不同,則存在bug。
independent 獨立的
編寫測試時,確定一次隻測試了一樣東西,但并不表示一個testmethod内隻能使用一個assert,而是一個測試函數應該專注于産品代碼中的一個函數,或者組合起來并共同提供某個特性的一組函數。
測試代碼必須同産品代碼相同的風格來編寫。這意味着你需要抽取出共同且重複的代碼,并把它們放到一個功能類之中,進而可以複用;單元測試的代碼一樣講究------維護封裝,dry原則,降低耦合。
何時需要mock對象
l 真實對象具有不可确定的行為(産生不可預測的結果,如股票的行情)
l 真實對象很難被建立(比如具體的web容器)
l 真實對象的某些行為很難觸發(比如網絡錯誤)
l 真實情況令程式的運作速度很慢
l 真實對象有使用者界面
l 測試需要詢問真實對象它是如何被調用的(比如測試可能需要驗證某個回調函數是否被調用了)
l 真實對象實際上并不存在(當需要和其他開發小組,或者新的硬體系統打交道的時候,這是一個普遍的問題)
mock對象的三個步驟
1. 原型
classa調用 classb的method()
2. 使用一個接口來描述這個對象
classa 通過接口調用classb的method
3. 以測試為目的,在mock對象中實作這個接口
全文完
作者:kkcat