本文是官方文檔的學習筆記,官方文檔在這里。
1、打開VS3013,隨便建一個解決方案,比如叫:LearnUnitTest,建一個類庫項目LearnUnitTest_Bank,該項目中添加一個BankAccount類,這個類及類中的方法就是我們要測試的對象。
2、給LearnUnitTest添加一個測試項目:在解決方案名稱上右鍵=》添加=》新建項目=》VisualC#=》測試=》單元測試項目,項目名稱叫LearnUnitTest_BankTest,將LearnUnitTest_Bank添加為LearnUnitTest_BankTest的引用項目,將測試項目LearnUnitTest_BankTest里默認生成的類重命名為BankAccountTest。
對於BankAccountTest類,類上有注解TestClass,方法上有注解TestMethod。可以在這類文件里添加其他類和方法,供測試方法使用。
首個測試:
3、現在我們測試BankAccount類的Debit方法,我們預先確定此次測試要檢查如下方面:
a、如果信用余額(credit amount)比賬戶余額大,該方法就拋異常ArgumentOutOfRangeException
b、如果信用余額小於0也拋異常
c、如果a和b都滿足,該方法會從賬戶余額里減去amount(函數參數)
注意:由a、b、c可以看郵BankAccount類中的Debit方法最后一行應該是-=,而不是+=——當然了,這個是故意留下的bug,而不是微軟的失誤,就等着在這次測試中把它測出來,然后修正掉。
在測試類里添加如下方法測試Debit方法:

// unit test code [TestMethod] public void Debit_WithValidAmount_UpdatesBalance() { // arrange double beginningBalance = 11.99; double debitAmount = 4.55; double expected = 7.44; BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance); // act account.Debit(debitAmount); // assert double actual = account.Balance; Assert.AreEqual(expected, actual, 0.001, "Account not debited correctly"); }
測試方法的要求:
必須要有TestMethod注解,返回類型為void,不能有參數。
經過測試,我們發現了bug,把+=改為-=即可。
使用單元測試改善代碼:
依然是測試Debit,本次測試想完成以下意圖:
a、如果credit amount(指的應該就是debit amount)比balance大,方法就拋ArgumentOutOfRangeException
b、如果credit amount比0小,也拋ArgumentOutOfRangeException異常
(1)創建測試方法
首次嘗試創建一個測試方法來處理上述問題:
代碼:

//unit test method [TestMethod] [ExpectedException(typeof(ArgumentOutOfRangeException))] public void Debit_WhenAmountIsLessThanZero_ShouldThrowArgumentOutOfRange() { // arrange double beginningBalance = 11.99; double debitAmount = -100.00; BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance); // act account.Debit(debitAmount); // assert is handled by ExpectedException }
注意這個方法:Debit_WhenAmountIsLessThanZero_ShouldThrowArgumentOutOfRange,意思是:當debit amount小於0時,本次測試應該會導致被測試的方法拋出ArgumentOutOfRange異常,否則本次測試就失敗了,沒有達到期望,需要修改Debit代碼以達成本次測試期望——正所謂TDD開發。
我們使用了ExpectedExceptionAttribute特性來斷言期望的異常應當被拋出。除非方法拋出ArgumentOutOfRangeException異常,否則該特性就會導致測試失敗(要注意本次測試的意圖)。用正的和負的debitAmount運行這個測試,然后臨時把被測試的方法(Debit方法)修改一下:當demit amount小於0時拋出一個ApplicatinException。搗騰完這些,發現本次測試基本沒什么問題。
為了測試debit amount 大於balance的情形,我們做下面幾個操作:
a、創建一個新的測試方法名叫 Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange
b、從上一個測試方法
Debit_WhenAmountIsLessThanZero_ShouldThrowArgumentOutOfRange
復制方法體到本測試方法
c、把debitAmount設置為一個比balance大的值
(2)運行測試方法
用不同的debitAmount值運行Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange
和 Debit_WhenAmountIsLessThanZero_ShouldThrowArgumentOutOfRange
然后運行三個測試,這樣我們最開始設定的三個cases都被覆蓋了。
(3)繼續分析
后面兩個測試方法Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange
和Debit_WhenAmountIsLessThanZero_ShouldThrowArgumentOutOfRange
有些問題:兩個測試運行的時候根據拋出的異常,你不知道是誰拋出的,靠ExpectedException特性做不到這件事。
可以這樣修改:
在類里定義兩個常量:

// class under test public const string DebitAmountExceedsBalanceMessage = "Debit amount exceeds balance"; public const string DebitAmountLessThanZeroMessage = "Debit amount less than zero"; // method under test // ... if (amount > m_balance) { throw new ArgumentOutOfRangeException("amount", amount, DebitAmountExceedsBalanceMessage); } if (amount < 0) { throw new ArgumentOutOfRangeException("amount", amount, DebitAmountLessThanZeroMessage); } // ...
(4)重構測試方法
首先,移除ExpectedException特性。取而代之的處理是:我們捕獲異常,來核實是在哪種條件下拋出的。
修改一下Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange
方法:

[TestMethod] public void Debit_WhenAmountIsGreaterThanBalance_ShouldThrowArgumentOutOfRange() { // arrange double beginningBalance = 11.99; double debitAmount = 20.0; BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance);\ // act try { account.Debit(debitAmount); } catch (ArgumentOutOfRangeException e) { // assert StringAssert.Contains(e.Message, BankAccount. DebitAmountExceedsBalanceMessage); } }
(5)再次測試,再次重寫,再次分析
當我們用不的參數再次運行測試方法Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange
的時候,會遇到下面一些問題:
1、如果我們使用一個比balance大的debitAmount運行,產生的測試結果是所期望的。
2、如果使用了一個debitAmount運行,使得assert 斷言失敗了(比如在Debit方法的某一行返回了一個非期望的異常),也沒什么問題,在本測試的情理之中。
3、如果debitAmount是有效的(比0大比balance小)會發生什么呢?沒有異常拋出,斷言也不會失敗,測試方法通過了。——這不是我們想要的,注意我們此次的測試初衷:要么斷言成功,要么斷言失敗,如果壓根進入不了斷言代碼,只能說明測試方法寫的問題!
為了解決這個問題,我們在測試方法的最后一行加入一個Fail斷言,來處理沒有異常發生的情況:沒有異常發生,就說明此次測試沒有達到期望!
但是修改好再次運行,會發現如果所期望的異常被捕獲了,測試總會失敗。為了解決這個問題,我們在StringAssert之前加一個return。
最終我們的Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange
方法如下:

[TestMethod] public void Debit_WhenAmountIsGreaterThanBalance_ShouldThrowArgumentOutOfRange() { // arrange double beginningBalance = 11.99; double debitAmount = 20.0; BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance);\ // act try { account.Debit(debitAmount); } catch (ArgumentOutOfRangeException e) { // assert StringAssert.Contains(e.Message, BankAccount. DebitAmountExceedsBalanceMessage); return; } Assert.Fail("No exception was thrown.") }
最終我們讓測試代碼變得更加強健,但更重要的是,在這個過程中,我也們也改善了被測試的代碼——這才是測試的最終目的。
微軟接下來講的是測試驅動開發。鏈接如下: