共用方式為


逐步解說:建立及執行單元測試以用於受管理的程式碼

本文將逐步引導您使用 Managed 程式代碼和 Visual Studio 測試總管的 Microsoft單元測試架構來建立、執行和自定義一系列單元測試,。 您可以從開發中的 C# 項目開始,建立執行其程式代碼的測試、執行測試,以及檢查結果。 然後,您可以變更專案程式代碼,然後重新執行測試。 如果您要在完成這些步驟之前概念性概觀這些工作,請參閱 單元測試基本概念。 如果您要從現有的程式代碼自動產生測試,請參閱 從程式代碼建立單元測試方法存根

建立要測試的專案

  1. 開啟 Visual Studio。

  2. 在 [開始] 視窗中,選擇 [[建立新專案]

  3. 搜尋並選取 .NET 的 C# 控制台應用程式 項目範本,然後按兩下 [下一步]

    注意

    如果您沒有看到 主控台應用程式 樣本,您可以從 [建立新的 專案] 視窗 安裝它。 在 找不到您要尋找的東西? 的訊息中,選擇 安裝更多工具和功能 連結。 然後,在 Visual Studio 安裝程式中,選擇 .NET 桌面開發 工作負載。

  4. 將專案命名 Bank,然後按 下一步

    選擇建議的目標 framework 或 .NET 8,然後選擇 建立

    Bank 專案已建立並顯示在 [方案總管] ,並在程式代碼編輯器中開啟了 Program.cs 檔案。

    注意

    如果未在編輯器中開啟 Program.cs,請按兩下 [方案總管] 中的檔案 Program.cs 加以開啟。

  5. Program.cs 的內容取代為下列 C# 程式代碼,以定義類別,BankAccount

    using System;
    
    namespace BankAccountNS
    {
        /// <summary>
        /// Bank account demo class.
        /// </summary>
        public class BankAccount
        {
            private readonly string m_customerName;
            private double m_balance;
    
            private BankAccount() { }
    
            public BankAccount(string customerName, double balance)
            {
                m_customerName = customerName;
                m_balance = balance;
            }
    
            public string CustomerName
            {
                get { return m_customerName; }
            }
    
            public double Balance
            {
                get { return m_balance; }
            }
    
            public void Debit(double amount)
            {
                if (amount > m_balance)
                {
                    throw new ArgumentOutOfRangeException("amount");
                }
    
                if (amount < 0)
                {
                    throw new ArgumentOutOfRangeException("amount");
                }
    
                m_balance += amount; // intentionally incorrect code
            }
    
            public void Credit(double amount)
            {
                if (amount < 0)
                {
                    throw new ArgumentOutOfRangeException("amount");
                }
    
                m_balance += amount;
            }
    
            public static void Main()
            {
                BankAccount ba = new BankAccount("Mr. Bryan Walton", 11.99);
    
                ba.Credit(5.77);
                ba.Debit(11.22);
                Console.WriteLine("Current balance is ${0}", ba.Balance);
            }
        }
    }
    
  6. 在 [方案總管] 中,以滑鼠右鍵按兩下並選擇 [重新命名],將檔案重新命名為 BankAccount.cs

  7. 在 [建置] 功能表上,按一下 [建置解決方案](或按 [ctrl + shift + B])。

您現在有一個專案,其中包含您可以測試的方法。 在本文中,測試著重於 Debit 方法。 Debit 方法會在從帳戶提取資金時呼叫。

建立單元測試專案

  1. 在 [檔案] 功能表上,選取 [[新增>新增專案]

    提示

    您也可以在 [方案總管] 中,滑鼠右鍵點擊方案,然後選擇 [新增>專案]

  2. 在搜尋方塊中輸入 測試,選取 C# 作為語言,然後選取適用於 .NET 範本的 C# MSTest Test Project,然後按兩下 [下一步]

    注意

    在Visual Studio 2019 16.9版中,MSTest專案模板是 單元測試專案

  3. 將專案命名 BankTests,然後按一下 下一步

  4. 選擇建議的目標 framework 或 .NET 8,然後選擇 建立

    從 Visual Studio 2022 17.10 版開始,您也可以選取測試執行器。 針對測試執行器,您可以選擇 VSTestMSTest。 如需測試執行器差異的詳細資訊,請參閱 Microsoft.Testing.Platform 和 VSTest 比較

    BankTests 專案會新增至 Bank 解決方案。

  5. BankTests 專案中,新增 Bank 專案的參考。

    [方案總管]中,選取 [BankTests] 專案底下的 [相依性],然後從滑鼠右鍵功能表選擇 [新增參考[或 新增專案參考]。

  6. 在 [參考管理員] 對話框中,展開 [專案],選取 [方案],然後檢查 Bank 專案。

  7. 選擇 [確定]

建立測試類別

建立測試類別來驗證 BankAccount 類別。 您可以使用專案範本所產生的 UnitTest1.cs 檔案,但為檔案和類別提供更具描述性的名稱。

重新命名檔案和類別

  1. 若要重新命名檔案,請在 [方案總管]中,選取 BankTests 專案中的 UnitTest1.cs 檔案。 從滑鼠右鍵功能表中,選擇 [重新命名] [或按 F2],然後將檔案重新命名為 BankAccountTests.cs

  2. 若要重新命名類別,請將游標放在程式碼編輯器 UnitTest1 上,以滑鼠右鍵按兩下,然後選擇 [重新命名] [或按 F2]。 輸入 BankAccountTests,然後按 Enter

BankAccountTests.cs 檔案現在包含下列程式代碼:

// The 'using' statement for Test Tools is in GlobalUsings.cs
// using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace BankTests
{
    [TestClass]
    public class BankAccountTests
    {
        [TestMethod]
        public void TestMethod1()
        {
        }
    }
}

新增using語句

using 語句 新增至測試類別,以在不使用完整名稱的情況下呼叫受測專案。 在類別檔案頂端,新增:

using BankAccountNS;

測試類別需求

測試類別的最低需求如下:

  • 任何類別上都需要 [TestClass] 屬性,其中包含您想要在 [測試總管] 中執行的單元測試方法。

  • 您想要測試總管辨識的每個測試方法都必須具有 [TestMethod] 屬性。

您可以在沒有 [TestClass] 屬性的單元測試項目中擁有其他類別,而且您可以在沒有 [TestMethod] 屬性的測試類別中擁有其他方法。 您可以從測試方法呼叫這些其他類別和方法。

建立第一個測試方法

在此程式中,您會撰寫單元測試方法來驗證 Debit 類別 BankAccount 方法的行為。

至少需要檢查三種行為:

提示

您可以刪除預設 TestMethod1 方法,因為在本逐步解說中不會使用它。

若要建立測試方法

第一項測試會確認有效金額(也就是小於帳戶餘額且大於零的金額)會從帳戶提取正確的金額。 將下列方法新增至該 BankAccountTests 類別:

[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 對象,然後提取有效的金額。 它會使用 Assert.AreEqual 方法來驗證期末餘額如預期。 Assert.AreEqualAssert.IsTrue等方法經常用於單元測試。 如需撰寫單元測試的詳細資訊,請參閱 撰寫測試

測試方法需求

測試方法必須符合下列需求:

  • 它以 [TestMethod] 屬性裝飾。

  • 它會傳回 void

  • 它不能有參數。

建置並執行測試

  1. 在 [建置] 功能表上,選擇 [建置方案] (或按 Ctrl + SHIFT + B)。

  2. 如果 [測試總管] 未開啟,請從頂端功能表欄選擇 [測試總管]>[測試總管] (或 測試>Windows>[測試總管]],或按 ctrl Ctrl + ET]。

  3. 選擇 [執行全部] 以執行測試(或按 Ctrl + RV)。

    當測試正在執行時, [測試總管] 視窗頂端的狀態欄會以動畫顯示。 在測試回合結束時,如果所有測試方法都通過,則長條會變成綠色,如果有任何測試失敗,則為紅色。

    在此情況下,測試會失敗。

  4. [測試總管] 中選取方法,來檢視視窗底部的詳細資訊。

修正程式代碼並重新執行測試

測試結果包含描述失敗的訊息。 您可能需要向下切入才能看到此訊息。 針對 AreEqual 方法,訊息會顯示預期內容和實際收到的內容。 您預期餘額會減少,但會隨著取款量而增加。

單元測試發現錯誤:減去時,將取款金額 新增至帳戶餘額。

更正錯誤

若要更正錯誤,請在 BankAccount.cs 檔案中,取代 這一行:

m_balance += amount;

跟:

m_balance -= amount;

重新執行測試

在 [測試總管]中,選擇 [執行所有] 以重新執行測試(或按 Ctrl + RV)。 紅色/綠色長條會變成綠色,表示測試通過。

在 Visual Studio 2019 中,測試總管 顯示通過的測試

在 Visual Studio 2019 中,測試總管 顯示通過的測試

使用單元測試來改善您的程序代碼

本節說明分析、單元測試開發和重構的反覆程式如何協助您讓您的生產程序代碼更健全且更有效率。

分析問題

您已建立測試方法,以確認 Debit 方法中已正確扣除有效金額。 現在,確認如果轉帳金額符合以下條件,方法是否會擲回 ArgumentOutOfRangeException

  • 大於餘額,或
  • 小於零。

建立並執行新的測試方法

建立測試方法,以在轉帳金額小於零時驗證正確的行為:

[TestMethod]
public void Debit_WhenAmountIsLessThanZero_ShouldThrowArgumentOutOfRange()
{
    // Arrange
    double beginningBalance = 11.99;
    double debitAmount = -100.00;
    BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance);

    // Act and assert
    Assert.ThrowsException<System.ArgumentOutOfRangeException>(() => account.Debit(debitAmount));
}

使用 ThrowsException 方法來確認是否拋出正確的例外狀況。 除非拋出 ArgumentOutOfRangeException,否則此方法將導致測試失敗。 如果您暫時修改受測方法,使其在轉帳金額小於零時拋出較通用的 ApplicationException,則測試行為正常,也就是失敗。

若要測試提取金額大於餘額的情況,請執行下列步驟:

  1. 建立名為 Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange的新測試方法。

  2. 將方法主體從 Debit_WhenAmountIsLessThanZero_ShouldThrowArgumentOutOfRange 複製到新的方法。

  3. debitAmount 設定為大於餘額的數位。

執行這兩個測試,並確認它們通過。

繼續分析

正在測試的方法可以進一步改善。 在目前的實作中,我們無法知道在測試期間拋出的異常是由哪個條件(amount > m_balanceamount < 0)導致的。 我們只知道在方法的某個地方拋出了一個 ArgumentOutOfRangeException。 如果我們能夠判斷 BankAccount.Debit 的哪個條件拋出例外狀況(amount > m_balanceamount < 0),就可以確信我們的方法在正確地驗證其參數。

再次查看正在測試的方法(BankAccount.Debit),並注意這兩個條件語句都使用只接受自變數名稱做為參數的 ArgumentOutOfRangeException 建構函式:

throw new ArgumentOutOfRangeException("amount");

您可以使用建構函式來報告更豐富的資訊:ArgumentOutOfRangeException(String, Object, String) 包含自變數的名稱、自變數值和使用者定義訊息。 您可以重構受測方法以使用此建構函式。 更棒的是,您可以使用公開可用的類型成員來指定錯誤。

重構受測試的程序代碼

首先,為類別範圍的錯誤訊息定義兩個常數。 將定義放在受測類別中,BankAccount

public const string DebitAmountExceedsBalanceMessage = "Debit amount exceeds balance";
public const string DebitAmountLessThanZeroMessage = "Debit amount is less than zero";

然後,修改 Debit 方法中的兩個條件語句:

if (amount > m_balance)
{
    throw new System.ArgumentOutOfRangeException("amount", amount, DebitAmountExceedsBalanceMessage);
}

if (amount < 0)
{
    throw new System.ArgumentOutOfRangeException("amount", amount, DebitAmountLessThanZeroMessage);
}

重構測試方法

藉由移除對 Assert.ThrowsException的呼叫來重構測試方法。 在 Debit() 區塊中包裹對 try/catch 的呼叫,捕捉預期的特定例外狀況,並驗證其相關聯的訊息。 Microsoft.VisualStudio.TestTools.UnitTesting.StringAssert.Contains 方法可讓您比較兩個字串。

現在,Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange 看起來可能像這樣:

[TestMethod]
public void Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange()
{
    // Arrange
    double beginningBalance = 11.99;
    double debitAmount = 20.0;
    BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance);

    // Act
    try
    {
        account.Debit(debitAmount);
    }
    catch (System.ArgumentOutOfRangeException e)
    {
        // Assert
        StringAssert.Contains(e.Message, BankAccount.DebitAmountExceedsBalanceMessage);
    }
}

重新測試、重寫和重新分析

目前,測試方法不會處理它應該處理的所有案例。 如果受測的方法 Debit 方法在 ArgumentOutOfRangeException 大於餘額(或小於零)時沒有擲回 debitAmount,則測試方法會通過。 這種情況不好,因為如果沒有拋出例外,您希望測試方法應該失敗。

這個結果是測試方法中的 Bug。 若要解決此問題,請在測試方法結尾新增 Assert.Fail 斷言,以處理未擲回例外的情況。

重新執行測試後顯示,如果捕捉到正確的異常,測試現在 失敗catch 區塊會攔截例外狀況,但方法仍然繼續執行,並在新的 Assert.Fail 斷言處失敗。 若要解決此問題,請在 return 區塊中的 StringAssert 後面新增 catch 語句。 重新執行測試會確認您已修正此問題。 Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange 的最終版本如下所示:

[TestMethod]
public void Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange()
{
    // Arrange
    double beginningBalance = 11.99;
    double debitAmount = 20.0;
    BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance);

    // Act
    try
    {
        account.Debit(debitAmount);
    }
    catch (System.ArgumentOutOfRangeException e)
    {
        // Assert
        StringAssert.Contains(e.Message, BankAccount.DebitAmountExceedsBalanceMessage);
        return;
    }

    Assert.Fail("The expected exception was not thrown.");
}

結論

測試程序代碼的改善導致更強固且資訊豐富的測試方法。 但更重要的是,它們也改善了受測程序代碼。

提示

本逐步解說會針對受控程式碼使用 Microsoft 單元測試架構。 測試總管 還可以從第三方單元測試框架中執行測試,但前提是這些框架具有 測試總管的適配器。 如需詳細資訊,請參閱 安裝第三方單元測試架構

如需如何從命令列執行測試的資訊,請參閱 VSTest.Console.exe 命令列選項