逐步解說:針對 Managed 程式碼建立和執行單元測試
本逐步解說會引導您建立、執行和自訂一系列的單元測試,將會逐步導引您使用 Managed 程式碼和 Visual Studio 測試總管的Microsoft 單元測試架構。 您可以從開發中的 C# 專案開始,建立執行其程式碼的測試、執行測試,並檢查結果。 然後,您可以修改專案程式碼並重新執行測試。
此主題包括下列章節:
注意事項 |
---|
本逐步解說使用 Managed 程式碼的 Microsoft 單元測試架構。測試總管也可以從協力廠商所支援測試總管的配接器的單元測試架構執行測試。如需詳細資訊,請參閱HOW TO:安裝協力廠商單元測試架構。 |
注意事項 |
---|
如需如何從命令列執行測試的資訊,請參閱逐步解說:使用命令列測試公用程式。 |
必要條件
- Bank 專案。 請參閱用於建立單元測試的範例專案。
準備逐步解說
若要準備逐步解說
開啟 Visual Studio 2012。
在 [檔案] 功能表上,指向 [新增],然後按一下 [專案]。
[新增專案] 對話方塊隨即出現。
在 [已安裝的範本] 下,按一下 [Visual C#]。
在應用程式類型清單中,按一下 [類別庫]。
在 [名稱] 方塊中,輸入 Bank,然後按一下 [確定]。
注意事項 如果已經有專案使用 "Bank" 這個名稱,就請為專案選擇另一個名稱。
新的 Bank 專案會建立並顯示在 [方案總管] 中,並於 [程式碼編輯器] 中開啟 Class1.cs 檔。
注意事項 如果 Class1.cs 檔並未在 [程式碼編輯器] 中開啟,請按兩下 [方案總管] 中的 Class1.cs 檔加以開啟。
複製用於建立單元測試的範例專案中的原始程式碼。
以用於建立單元測試的範例專案中的程式碼取代 Class1.cs 的原始內容。
另存新檔成 BankAccountTest.cs 檔案。
在 [建置] 功能表上,按一下 [建置方案]。
現在您已經有一個名為 Bank 的專案。 其中包含要測試的原始程式碼和用來測試它的工具。 Bank 的命名空間 BankAccountNS,包含了公用類別 BankAccount,您將會在下列程序中測試其方法。
在快速啟動,我們著重於 Debit 方法。當從帳戶提領錢並包含下列程式碼時, Debit 方法呼叫:
// method under test
public void Debit(double amount)
{
if(amount > m_balance)
{
throw new ArgumentOutOfRangeException("amount");
}
if (amount < 0)
{
throw new ArgumentOutOfRangeException("amount");
}
m_balance += amount;
}
建立單元測試專案
必要條件:遵循準備逐步解說程序中的步驟。
若要建立單元測試專案
在檔案 功能表中選擇加入,然後選擇新增專案。
在新的專案對話方塊中,展開已安裝的,Visual C#,然後按一下測試。
在專案範本清單中選取 單位測試專案。
在名稱方塊中,輸入 BankTest,然後選取確定。
BankTests專案加入至Bank方案。
在BankTests專案中,加入Bank方案的參考。
在方案總管中,選擇參考從BankTests專案。然後從內容功能表選取加入參考...。
在參考管理員對話方塊中,展開方案,然後檢查Bank項目。
建立測試類別
我們需要一個測試類別以驗證 BankAccount 類別。 我們可以使用由專案範本所產生的 UnitTest1.cs,不過,我們應該給檔案和類別更描述性的名稱。 我們可以在方案總管中重新命名這些檔案,在一個步驟達成。
重新命名類別檔案。
在方案總管中,選取 BankTests 專案的 UnitTest1.cs 檔案。 從內容功能表中,選擇 重新命名,然後將檔案重新命名為 BankAccountTests.cs。 如果您要專案中所有參考重新命名為程式碼項目「UnitTest1」,選取存取的對話方塊的是。 此步驟會將類別名稱變更為BankAccountTest。
BankAccountTests.cs 檔案會包含下列程式碼:
// unit test code
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace BankTests
{
[TestClass]
public class BankAccountTests
{
[TestMethod]
public void TestMethod1()
{
}
}
}
若要加入 using 陳述式加入至專案進行測試
我們也可以將 using 陳述式加入至類別讓我們呼叫 Project 進行測試,而不需使用完整名稱。 在類別檔案頂端,加入:
using BankAccountNS
測試類別要求
測試類別的最低需求如下:
[TestClass] 屬性對於Managed 程式碼的Microsoft 單元測試架構是必要的,對要執行的任何類別且加入測試總管中的單元測試方法。
每個測試方法要測試總管執行必須有 [TestMethod]屬性。
您可以在沒有 [TestClass] 屬性在單元測試專案的其他類別,因此,您可以在沒有 [TestMethod] 屬性的測試類別的其他方法。 您可以在測試方法可以使用這些其他類別和方法。
建立第一個測試方法
在這個程序中,我們會撰寫單元測試方法驗證 BankAccount 類別的 Debit 方法的行為。 方法如上所列。
透過分析待測方法,將決定至少有三種行為需要核取:
如果賒帳金額大於平衡,方法會擲回 [ArgumentOutOfRangeException] 。
如果賒帳金額小於零,則也會擲回 ArgumentOutOfRangeException 。
如果在 1.) 和 2.) 的檢查滿足,方法會從帳戶餘額減去數量。
在我們的第一個測試,以確認有效的帳戶(低於帳戶餘額,而且大於零) 會提領正確數量的現金。
若要建立測試方法
若要加入 using BankAccountNS; 陳述式至BankAccountTests.cs 檔案。
將下列方法加入至 BankAccountTests類別:
// 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"); }
方法相當簡單。 我們設定一開頭平衡的新 BankAccount 物件,然後提領有效數量。 我們使用 Microsoft 單元測試架構為 Managed 程式碼 AreEqual 方法,可以確認結尾平衡就是我們預期。
測試方法需求
測試方法必須符合下列需求:
此方法必須以 [TestMethod]屬性裝飾。
方法必須傳回 void。
方法不能有參數。
建置並執行測試
若要建置及執行測試
在 [建置] 功能表上,選擇 [建置方案]。
如果未發生錯誤, UnitTestExplorer 視窗隨即出現在Debit_WithValidAmount_UpdatesBalance未執行的測試群組中。 如果在已完成的組建,測試總管沒有出現,選取功能表上的測試之後,請選取Windows,然後選取 測試總管。
選取全部執行執行測試。 在執行測試時,在視窗頂端的狀態列顯示為動畫。 在測試回合結束時,如果狀態列變成綠色,則所有測試方法成功,或紅色代表有測試失敗。
在此情況下,測試就會失敗。 測試方法移至失敗的測試。 參考。 在視窗的底部,選取測試總管的方法檢視詳細資料。
修正程式碼並重新執行測試。
分析測試結果
測試結果包含說明錯誤的訊息。 AreEquals 方法的資訊,向您顯示預期的時間 (Expected<XXX>(參數),以及實際收到 Actual<YYY> (參數)。 我們預期平衡從開始的平衡下降,然而相反地,它增加了領出量。
在Debit程式碼執行檢查運算式,可以知道單元測試有助於發現 Bug。 領出的量被加入至帳戶餘額,而非減去。
修正 Bug
若要更正這個錯誤,請取代線條
m_balance += amount;
with
m_balance -= amount;
重新執行測試。
在測試總管中,選取全部執行重新執行測試。 紅色、綠色長條轉成綠色,且測試會移至通過的測試群組。
使用單元測試改善您的程式碼
本章節說明分析、單元測試、開發和重構迴圈處理序如何協助您,讓您的實際執行程式碼更穩固且有效的。
分析問題
在建立測試方法檢查在 Debit 方法有效數量正確推算後,我們可移到我們原始程式碼剖析的剩餘的情況:
如果賒帳金額大於平衡,方法會擲回 ArgumentOutOfRangeException 。
如果賒帳金額小於零,則也會擲回 ArgumentOutOfRangeException 。
建立測試方法
在建立測試方法的第一次嘗試解決這些問題似乎會為:
//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
}
我們使用 ExpectedExceptionAttribute 屬性判斷提示正確的例外狀況。 除非 ArgumentOutOfRangeException 擲回例外狀況,否則屬性會造成測試失敗。 執行與正數與負數的 debitAmount 值,接著暫時修改方法,在數量小於零時擲回泛型 ApplicationException ,測試是否正確運作。 若要測試案例提領的數量大於平衡時,我們全部需要進行的是:
建立名為 Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange 的新方法。
複製 Debit_WhenAmountIsLessThanZero_ShouldThrowArgumentOutOfRange 的方法主體加入至新方法。
設定 debitAmount 數字成大於平衡組態。
執行測試。
使用不同值的兩個方法執行 debitAmount ,示範測試可以完全處理我們剩下的情況。 執行所有三項測試會確認在我們原始程式碼剖析的所有情況正確報告。
繼續程式碼剖析
不過,最後兩個測試方法也有些令人困擾。 我們無法確定在程式碼符合測試下,在哪一個測試回合會擲回。 區分這兩個條件的某種方式會很有用。 我們進一步考慮這個問題,很明顯得知哪一種情況違反了將增加我們對測試的信賴等級。 這項資訊也很可能在處理例外狀況的實際執行機制很有用,因為它進行測試時的方法會擲回。 當方法擲回,產生更多資訊是協助,不過 ExpectedException 屬性無法提供這項資訊。
再次查看在測試中的方法,將會看到兩個條件陳述式使用接受引數的名稱做為參數的 ArgumentOutOfRangeException 建構函式:
throw new ArgumentOutOfRangeException("amount");
從 MSDN Library 中搜尋,以尋找建構函式存在試算表更豐富的資訊。 ArgumentOutOfRangeException(String, Object, String) 包含引數、引數值和一個使用者定義的訊息的名稱。 我們可以重構待測方法以使用這個建構函式。 更好,我們可以使用可公開取得的型別成員指定錯誤。
重構待測程式碼。
我們先在類別範圍定義錯誤訊息的兩個常數:
// class under test
public const string DebitAmountExceedsBalanceMessage = "Debit amount exceeds balance";
public const string DebitAmountLessThanZeroMessage = "Debit amount less than zero";
我們再修改 Debit 方法的兩個條件陳述式:
// method under test
// ...
if (amount > m_balance)
{
throw new ArgumentOutOfRangeException("amount", amount, DebitAmountExceedsBalanceMessage);
}
if (amount < 0)
{
throw new ArgumentOutOfRangeException("amount", amount, DebitAmountLessThanZeroMessage);
}
// ...
重構測試方法
在我們的測試方法,我們先移除 ExpectedException 屬性。 在其原本的位置中,我們會攔截擲回的例外狀況並確認它在正確的條件陳述式擲回例外狀況。 不過,我們現在必須決定在兩個選項之間驗證我們剩下的情況。 例如在 Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange 方法,可以採取下列動作:
判斷提示例外狀況 ( ArgumentOutOfRangeException 建構函式的第二個參數)的 ActualValue 屬性的大於開頭平衡。 選項必須要測試例外狀況的 ActualValue 屬性,針對測試方法的 beginningBalance 變數,因此需要驗證 ActualValue 大於零。
判斷提示訊息 (建構函式的第三個參數)。 BankAccount 類別中定義的 DebitAmountExceedsBalanceMessage 。
Microsoft 單元測試架構的 StringAssert.Contains 方法讓我們驗證第二個選項,而不需要在慣用的計算。
在修改 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);
}
}
重新測試,重新撰寫,並重新分析
當我們以不同的值的測試方法重新測試,我們遇到下列幾點:
如果使用比平衡大debitAmount 以攔截到正確的錯誤,Contains 判斷提示將會傳遞,例外狀況會被忽略,因此測試方法會成功。 這是我們所要的行為。
如果我們使用 debitAmount,判斷提示就會失敗,因為錯誤訊息傳回。 如果我們在方法的另一個指向測試程式碼路徑下,引進了暫存 ArgumentOutOfRange 例外狀況,判斷提示也會失敗。 這也會比較好。
如果 debitAmount 值是有效的 (也就是說,小於平衡,但大於零,例外狀況未被攔截,因此,判斷提示就不會被攔截。 測試方法就會成功。 這不好,因為我們要在例外狀況不會被擲回時,測試的方法失敗。
第三個事實是我們的測試方法中的錯誤。 若要嘗試解決這個問題,我們在測試方法結束時增加 Fail 判斷提示,處理例外狀況不會被擲回的情況。
但是重新測試顯示,如果截獲正確的例外狀況,測試就會失敗。 Catch 陳述式重設例外狀況,且方法會繼續執行,無法在新的判斷提示。 若要剖析新的問題,我們在 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.")
}
在這個最後一部分工作,我們所做的改進我們測試程式碼會產生更強大且更具資訊性的測試方法。 但是更重要的是,在我們的專案額外的分析可能會得到較佳的受測試程式碼。