// Action & Assert
stack.Push(emptyString);
Assert.AreEqual(emptyString, stack.Top());
stack.Push(nullString);
Assert.AreEqual(nullString, stack.Top());
}
[TestMethod]
public void Test_Success_PopLastTwoElements()
{
// Arrange
var stack = new StackExercise();
var testElement1 = "test1";
var testElement2 = "test2";
// Action & Assert
stack.Push(testElement1);
stack.Push(testElement2);
Assert.AreEqual(testElement2, stack.Top());
stack.Pop();
Assert.IsFalse(stack.Empty());
Assert.AreEqual(testElement1, stack.Top());
stack.Pop();
Assert.IsTrue(stack.Empty());
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void Test_ThrowException_PopFromEmptyStack()
{
var stack = new StackExercise();
stack.Pop();
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void Test_ThrowException_TopFromEmptyStack()
{
var stack = new StackExercise();
stack.Top();
}
}
}
所以,究竟該如何寫單元測試呢?《單元測試之道C#版》里總結得很好:Right-BICEP。
Right,驗證結果(主要功能和邏輯)是否正確;
B,邊界條件是否正確;
I,是否可以檢查反向關聯;這里所謂反向關聯,是指用反向邏輯來驗證我們的結果,比如說要驗證平方根是否正確時,可以求這個平方根的平方跟我們的輸入是否一致。
C,是否可以采用其他方法來cross-check結果;cross-check是在單元測試中采用與實際模塊中不同的方法來實現同樣的功能作為期望結果,去與實際模塊中得到的結果做對比。
E,錯誤條件是否可以重現;
P,性能方面是否滿足條件。
3. 單元測試中不得不說的知識點
(1)斷言Assertion
要驗證代碼的行為是否與期望一致時,我們需要使用斷言來判斷某個語句為真或為假,以及某些結果值與期望值是否相等,如IsTrue()、IsFalse()、AreEqual()等。
Assert.AreEqual(expected, actual [, string message]);
其中前兩個參數很好理解,分別為期望值和實際值,最后一個可選參數是發生錯誤時報告的消息。如果不提供的話,出錯后會看到這樣的error message:Assert.AreEqual failed. Expected: xx. Actual: yy.。如果你的那個單元測試函數中有很多Assert.AreEqual的話,你就不清楚究竟是在哪個Assertion出錯的,而當你對每個Assertion放上相應的message的話,出錯時就可以一眼看出具體出錯的Assertion。
另外,在用斷言進行浮點數的比較時還需要提供另外一個參數tolerance。
有時候每個test里我們都需要進行一系列相同或者類似的斷言,那么我們可以嘗試編寫自定義的斷言,這樣測試的時候使用這個自定義的斷言即可。
(2)test 組成
從上面的例子可以看到,test project與普通project的區別就是在class和method上面增加了一個屬性。在不同的框架下這些屬性還是不一樣的,比如說我們上面用到的VS里自帶的test框架,使用的是[TestClass]和[TestMethod],而大家最常用的NUint框架則使用的是[TestFixture]和[Test]。
另外,還有幾個attribute在實際項目中我們也會經常用到,那就是[SetUp]、[TearDown]、[TestFixtureSetUp]和[TestFixtureTearDown]。它們用來在調用test之前設置測試環境和在test之后釋放資源。前兩個是per-method,即每個用[Test]修飾的方法在運行前后都會調用[SetUp]和[TearDown];而后兩個則是per-class的,即用于[TestFixture]修飾的類的前后。
(3)對于異常的測試
對于預期的異常,只要在測試方法上添加[ExpectedException(typeof(YourExpectedExcetion))]屬性即可。但是需要注意的是,一旦這個方法期望的異常拋出了,測試方法中剩余的代碼就會被跳過。
所以NUint里面還有一種方式來驗證異常,即Assert.Throws(() => methodToTest());,這樣就可以在一個test method里面驗證多個拋出異常的情況了。
(4)使用mock對象
單元測試的目標是一次只驗證一個方法或一個類,但是如果這個方法依賴一些其他難以操控的東西,比如網絡、數據庫等。這時我們就要使用mock對象,使得在運行unit test的時候使用的那些難以操控的東西實際上是我們mock的對象,而我們mock的對象則可以按照我們的意愿返回一些值用于測試。
原文轉自:http://www.jianshu.com/p/7984955720e2