在上篇文章中提到過,單元測試是程序員編寫的代碼,用于驗證某段代碼的行為是否與開發(fā)者所期望的一致,并且說明了它的重要性,現(xiàn)在是時候來看看在實際開發(fā)中如何去做了。
為了驗證代碼行為是否與我們所期望的一致,就需要使用斷言(assertion)。斷言就是對待測代碼的結(jié)果進(jìn)行檢查,判斷是否與期望的一致。比如下面的IsTrue方法,它檢查給定的條件是否為真:如果非真,則斷言失敗,程序中止。
public void IsTrue(bool condition)
{
if (!condition)
{
Abort();
}
}
比如,有如下代碼:
int a = 2;
IsTrue(a == 2);
如果a不等于2,那么程序會退出。不難理解,所有類似的斷言都可以使用此方法。但情況卻不都像a == 2這樣簡單,我們要斷言的值可能是浮點數(shù)、字符串、集合等等很多情況,如果都使用IsTrue,那么在調(diào)用前我們都要考慮如何進(jìn)行比較,而且我們的處理也不止程序中止這種方式,好像很復(fù)雜啊。
如果有一個專門處理這些斷言的工具多好啊。很幸運,已經(jīng)有了,我們可以選擇NUnit或者M(jìn)bUnit。我們這里先看看NUnit,它是.NET這邊最正統(tǒng)的單元測試工具。NUnit提供了自己的GUI工具,我們可以通過該工具測試,但是有更好的選擇——TestDriven.NET,有了它,就不需要在開發(fā)環(huán)境和測試工具間來回切換了,單元測試在VS內(nèi)就可以完成。
在NUnit中,有個Assert類,它提供了多種斷言方式,可以滿足大部分需要了。比如針對兩個整數(shù)是否相等的斷言,可以使用Assert.AreEqual,我們沒必要寫自己的IsTrue方法了,只要調(diào)用:Assert.AreEqual(2, a)即可。
好,現(xiàn)在從一個簡單的例子開始,看一下如何使用NUnit進(jìn)行單元測試。
計劃你的測試
我們考慮如下一個方法,它查找一個int數(shù)組中的元素的最大值:
static int Largest(int[] array);
給定一個數(shù)組[7, 8, 9],該方法應(yīng)該返回9(這就是我們的期望值)。等一下,你有沒有想到其它的測試呢?思考一分鐘再往下看。
首先,元素的位置應(yīng)與返回值無關(guān),所以可想到如下測試:
l [7, 8, 9] -> 9
l [8, 9, 7] -> 9
l [9, 7, 8] -> 9
接下來,如果數(shù)組中有兩個相等的最大值,該是如何?
l [7, 9, 8, 9] -> 9
還有,如果數(shù)組只有一個元素,會是怎樣?
l [1] -> 1
別忘了整數(shù)包含負(fù)數(shù)呢:
l [-9, -8, -7] -> -7
還不寫代碼啊,我早就想到它的實現(xiàn)方法了:
public static int Largest(int[] array)
{
int index, max = Int32.MaxValue;
for (int index = 0; index < array.Length - 1; index++)
{
if (array[index] > max)
{
max = array[index];
}
return max;
}
}
測試一個簡單的方法
我們來給上面的方法編寫一個測試用例(Test Case):
[TestFixture]
public class MyUtilTest
{
[Test]
public void LargestOf3()
{
Assert.AreEqual(9, MyUtil.Largest(new int[]{8, 9, 7}));
}
}
好,激動人心的時刻到了,運行下看看。
不好,測試沒通過,出現(xiàn)了下面的信息
TestCase 'Ch02.MyUtilTest.LargestOf3' failed:
Expected: 9
But was: 2147483647
D:\myWorks\VS2008\Consoles\UnitTestingInAction\Ch02\MyUtilTest.cs(22,0): at Ch02.MyUtilTest.LargestOf3()
結(jié)果與期望不一致,輸入的是7、8、9,怎么會返回那么大的數(shù)字呢?看看代碼,max變量的初始值為Int32.MaxValue,這就不對了,如果改成0的話就對了。此時測試確實是通過的。
上面我們想到了很多測試還沒做呢,先考慮最大值的位置,這個跟結(jié)果應(yīng)當(dāng)是無關(guān)的。
再添加兩個斷言,代碼還是寫在剛才的測試用例中。
Assert.AreEqual(9, MyUtil.Largest(new int[] { 8, 9, 7 }));
Assert.AreEqual(9, MyUtil.Largest(new int[] { 9, 8, 7 }));
Assert.AreEqual(9, MyUtil.Largest(new int[] { 7, 8, 9 }));
現(xiàn)在該通過了,運行測試。
暈,又沒通過:
TestCase 'Ch02.MyUtilTest.LargestOf3' failed:
Expected: 9
But was: 8
D:\myWorks\VS2008\Consoles\UnitTestingInAction\Ch02\MyUtilTest.cs(24,0): at Ch02.MyUtilTest.LargestOf3()
第三個斷言失敗,最大值是8,你的直覺是什么?是不是好像9根本沒執(zhí)行到呢?檢查下for循環(huán):
for (index = 0; index < array.Length - 1; index++)
通常我們會寫index < array.Length,這里顯然少循環(huán)了一次,這是個事故多發(fā)地帶,這個錯誤被稱為“off-by-one”錯誤,如果使用foreach語句就不存在這個問題了。
現(xiàn)在測試終于通過了,再看看重復(fù)最大值和單一元素的斷言:
[Test]
public void TestDups()
{
Assert.AreEqual(9, MyUtil.Largest(new int[] { 8, 9, 7, 9 }));
}
[Test]
public void TestOne()
{
Assert.AreEqual(1, MyUtil.Largest(new int[] { 1 }));
}
至此一切正常,Yeah!等等,如果元素是負(fù)數(shù)呢?
Assert.AreEqual(-7, MyUtil.Largest(new int[] { -9, -8, -7 }));
TestCase 'Ch02.MyUtilTest.TestNegative' failed:
Expected: -7
But was: 0
D:\myWorks\VS2008\Consoles\UnitTestingInAction\Ch02\MyUtilTest.cs(42,0): at Ch02.MyUtilTest.TestNegative()
0是哪里來的?看來又是初始值的問題,0要大于負(fù)數(shù),看來初始值必須是一個最小的整數(shù),它就是Int32.MinValue,這樣就可以了。
最后,如果array為空(長度為0)怎么辦?這個時候最大值是無意義的,返回任何值都不合適,應(yīng)當(dāng)拋出一個異常,代碼修改如下:
int index, max = Int32.MinValue;
if (array.Length == 0)
{
throw new ArgumentException("largest: Empty array.");
}
for (index = 0; index < array.Length; index++)
…
注意,代碼在設(shè)計上發(fā)生了改動,改善設(shè)計正是單元測試的好處之一。
為之編寫測試用例:
[Test, ExpectedException(typeof(ArgumentException))]
public void TestEmpty()
{
MyUtil.Largest(new int[] { });
}
注意,這個方法的特性中,除了Test,還多了個ExpectedException,與前面的用例不同,我們期望的正是異常。
小結(jié)
本文通過一個簡單的例子描述了單元測試的過程,從此我們也可以編寫測試用例了,對其有了初步的認(rèn)識。其中的過程有些繁瑣,也許你會問,這么一個簡單的方法值得花費這么大的力氣嗎?答案是肯定的,單元測試保證了程序在當(dāng)前的質(zhì)量,而在維護(hù)時會體現(xiàn)出更大的價值。
文中用到了NUnit和TestDriven.NET兩個工具,其詳細(xì)用法可以在園子里搜一下,在后續(xù)文章中我也不想再寫相關(guān)內(nèi)容了。從下一篇開始將逐步深入地討論單元測試的實施過程。
如對本文有疑問,請?zhí)峤坏浇涣髡搲瑥V大熱心網(wǎng)友會為你解答??! 點擊進(jìn)入論壇