教學課程: 使用 Visual Studio 搭配 .NET 測試 .NET 類別庫

本教學課程示範如何新增測試專案至解決方案,以自動化單元測試。

必要條件

建立單元測試專案

單元測試能在開發與發佈期間提供自動化的軟體測試。 MSTest 是三個可供選用的測試架構之一。 另外兩個架構分別是 xUnitnUnit

  1. 啟動 Visual Studio。

  2. 開啟您在 使用 Visual Studio .NET 類別庫時,建立的 ClassLibraryProjects 解決方案。

  3. 將名為 "StringLibraryTest" 的單元測試專案新增到解決方案。

    1. 在 [方案總管] 中以滑鼠右鍵按一下該解決方案,然後選取 [新增]>[新專案]

    2. 在 [新增專案] 頁面上的搜尋方塊中輸入 mstest。 從語言清單中選擇 [C#] 或 [Visual Basic],然後從平台清單中選擇 [所有平台]

    3. 選擇 [MSTest 測試專案] 範本,然後選擇 [下一步]

    4. 在 [設定新專案] 頁面上的 [專案名稱] 方塊中輸入 StringLibraryTest。 接著,選擇 [下一步]

    5. 在 [其他資訊] 頁面上的 [Framework] 方塊中,選取 [.NET 8 (預覽)]。 接著,選擇 [建立]

  4. Visual Studio 會建立專案,並在程式碼視窗中開啟類別檔案,如以下程式碼所示。 若未顯示您想用的語言,請變更頁面頂端的語言選取器。

    namespace StringLibraryTest;
    
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void TestMethod1()
        {
        }
    }
    
    Imports Microsoft.VisualStudio.TestTools.UnitTesting
    
    Namespace StringLibraryTest
        <TestClass>
        Public Class UnitTest1
            <TestMethod>
            Sub TestSub()
    
            End Sub
        End Class
    End Namespace
    

    單元測試範本建立的原始程式碼會執行下列動作︰

    執行單元測試時,標記為 [TestClass] 的測試類別中具有 [TestMethod] 標記的每個方法都會自動執行。

新增專案參考

若要讓測試專案使用 StringLibrary 類別,請將 StringLibraryTest 專案中的參考新增至 StringLibrary 專案。

  1. 方案總管 中,以滑鼠右鍵按一下 StringLibraryTest 專案的 [相依性] 節點,然後從內容功能表中選取 [新增專案參考]

  2. [參考管理員] 對話方塊中,展開 [專案] 節點並選取 [StringLibrary] 旁的方塊。 新增參考到 StringLibrary 元件能讓編譯器在編譯 StringLibraryTest 專案時尋找 StringLibrary 方法。

  3. 選取 [確定]。

新增及執行單元測試方法

Visual Studio 執行單元測試時,會執行所有標記 TestMethodAttribute 屬性的類別中標記了 TestClassAttribute 屬性的方法。 測試方法會在發現第一次失敗時,或方法中包含的所有測試完成後結束。

最常見的測試會呼叫 Assert 類別的成員。 許多判斷提示方法都至少包括兩個參數,一個是預期的測試結果,另一個是實際的測試結果。 下表顯示 Assert 類別最常呼叫的方法之一:

Assert 方法 函式
Assert.AreEqual 驗證兩個值或物件相等。 如果值或物件不相等,判斷提示就會失敗。
Assert.AreSame 驗證兩個物件變數參考相同的物件。 如果變數參考不同的物件,判斷提示就會失敗。
Assert.IsFalse 驗證條件為 false。 如果條件為 true,判斷提示就會失敗。
Assert.IsNotNull 驗證物件並非 null。 如果物件為 null,判斷提示就會失敗。

您也可以在測試方法中使用 Assert.ThrowsException 方法,指出預期應擲回的例外狀況類型。 如果指定的例外狀況沒有擲回,測試便會失敗。

在測試 StringLibrary.StartsWithUpper 方法時,您想提供幾個以大寫字元開頭的字串。 您預期此方法在這些情況下傳回 true,因此您可以呼叫 Assert.IsTrue 方法。 同樣地,您想提供幾個開頭不是大寫字元的字串。 您預期此方法在這些情況下傳回 false,因此您可以呼叫 Assert.IsFalse 方法。

既然您的程式庫方法可以處理字串,建議您也一併確認是否能夠成功處理空字串 (String.Empty),亦即不包含任何字元且其 Length 為 0 的有效字串和尚未初始化的 null 字串。 您可以直接呼叫 StartsWithUpper 作為靜態方法,並傳遞單一 String 引數。 也可以在指派給 nullstring 變數上呼叫 StartsWithUpper 作為擴充方法。

您接下來要定義三個方法,每個方法會針對字串陣列中的各個元素呼叫 Assert 方法。 您會呼叫方法多載,以供您指定要在測試失敗後顯示的錯誤訊息。 此訊息會辨識造成失敗的字串。

建立測試方法:

  1. UnitTest1.csUnitTest1.vb 程式碼視窗中,以下列程式碼取代程式碼:

    using UtilityLibraries;
    
    namespace StringLibraryTest
    {
        [TestClass]
        public class UnitTest1
        {
            [TestMethod]
            public void TestStartsWithUpper()
            {
                // Tests that we expect to return true.
                string[] words = { "Alphabet", "Zebra", "ABC", "Αθήνα", "Москва" };
                foreach (var word in words)
                {
                    bool result = word.StartsWithUpper();
                    Assert.IsTrue(result,
                           string.Format("Expected for '{0}': true; Actual: {1}",
                                         word, result));
                }
            }
    
            [TestMethod]
            public void TestDoesNotStartWithUpper()
            {
                // Tests that we expect to return false.
                string[] words = { "alphabet", "zebra", "abc", "αυτοκινητοβιομηχανία", "государство",
                                   "1234", ".", ";", " " };
                foreach (var word in words)
                {
                    bool result = word.StartsWithUpper();
                    Assert.IsFalse(result,
                           string.Format("Expected for '{0}': false; Actual: {1}",
                                         word, result));
                }
            }
    
            [TestMethod]
            public void DirectCallWithNullOrEmpty()
            {
                // Tests that we expect to return false.
                string?[] words = { string.Empty, null };
                foreach (var word in words)
                {
                    bool result = StringLibrary.StartsWithUpper(word);
                    Assert.IsFalse(result,
                           string.Format("Expected for '{0}': false; Actual: {1}",
                                         word == null ? "<null>" : word, result));
                }
            }
        }
    }
    
    Imports Microsoft.VisualStudio.TestTools.UnitTesting
    Imports UtilityLibraries
    
    Namespace StringLibraryTest
        <TestClass>
        Public Class UnitTest1
            <TestMethod>
            Public Sub TestStartsWithUpper()
                ' Tests that we expect to return true.
                Dim words() As String = {"Alphabet", "Zebra", "ABC", "Αθήνα", "Москва"}
                For Each word In words
                    Dim result As Boolean = word.StartsWithUpper()
                    Assert.IsTrue(result,
                           $"Expected for '{word}': true; Actual: {result}")
                Next
            End Sub
    
            <TestMethod>
            Public Sub TestDoesNotStartWithUpper()
                ' Tests that we expect to return false.
                Dim words() As String = {"alphabet", "zebra", "abc", "αυτοκινητοβιομηχανία", "государство",
                                   "1234", ".", ";", " "}
                For Each word In words
                    Dim result As Boolean = word.StartsWithUpper()
                    Assert.IsFalse(result,
                           $"Expected for '{word}': false; Actual: {result}")
                Next
            End Sub
    
            <TestMethod>
            Public Sub DirectCallWithNullOrEmpty()
                ' Tests that we expect to return false.
                Dim words() As String = {String.Empty, Nothing}
                For Each word In words
                    Dim result As Boolean = StringLibrary.StartsWithUpper(word)
                    Assert.IsFalse(result,
                           $"Expected for '{If(word Is Nothing, "<null>", word)}': false; Actual: {result}")
                Next
            End Sub
        End Class
    End Namespace
    

    TestStartsWithUpper 方法中的大寫字元測試包括希臘文大寫字母 alpha (U+0391) 和斯拉夫文大寫字母 EM (U+041C)。 TestDoesNotStartWithUpper 方法中的小寫字元測試包括希臘文小寫字母 alpha (U+03B1) 和斯拉夫文小寫字母 Ghe (U+0433)。

  2. 在功能表列上,選取 [檔案]>[另存 UnitTest1.cs 為] 或 [檔案]>[另存 UnitTest1.vb 為]。 在 [另存新檔] 對話方塊中,選擇 [儲存] 按鈕旁的箭號,然後選擇 [以編碼方式儲存]*。

    Visual Studio Save File As dialog

  3. [確認另存新檔] 對話方塊中,選擇 [是] 按鈕以儲存檔案。

  4. [進階儲存選項] 對話方塊的,選取 [編碼] 下拉式清單中選擇 [Unicode (UTF-8 有簽章) - 字碼頁 65001],然後選擇 [確定]

    Visual Studio Advanced Save Options dialog

    如果您無法將您的原始程式碼儲存為 UTF8 編碼檔案,Visual Studio 可能會將它儲存為 ASCII 檔案。 若發生這種情況,執行階段便無法正確解碼 ASCII 範圍之外的 UTF8 字元,因此測試結果會有錯誤。

  5. 在功能表列上,選擇 [測試]>[執行所有測試]。 如果 [測試總管] 視窗未開啟,請藉由選取 [測試]>[測試總管]加以開啟。 這三項測試都會列在 [通過的測試] 區段中,而 [摘要] 區段則報告測試回合的結果。

    Test Explorer window with passing tests

處理測試失敗

如果您正在執行測試驅動開發 (TDD),請先轉寫測試,並在第一次執行測試時失敗。 接著,在應用程式中新增可以使測試成功的程式碼。 在本教學課程中,您先撰寫應用程式程式碼,才建立用於驗證的測試,因此測試尚未失敗過。 為了驗證在測試預計會失敗的清況下,測試會確實失敗,請在測試輸入中加入不無效值。

  1. 修改 TestDoesNotStartWithUpper 方法中的 words 陣列,以包含字串「錯誤」。 您不需要儲存檔案,因為當組建方案以執行測試時,Visual Studio 會自動儲存開啟的檔案。

    string[] words = { "alphabet", "Error", "zebra", "abc", "αυτοκινητοβιομηχανία", "государство",
                       "1234", ".", ";", " " };
    
    Dim words() As String = { "alphabet", "Error", "zebra", "abc", "αυτοκινητοβιομηχανία", "государство",
                       "1234", ".", ";", " " }
    
    
  2. 從功能表列中,選取 [測試]>[執行所有測試] 來執行測試。 [測試總管] 視窗表示兩個測試成功,而且有一項失敗。

    Test Explorer window with failing tests

  3. 選取失敗的測試,TestDoesNotStartWith

    [測試總管] 下方的窗格會顯示判斷提示產生的訊息:「Assert.IsFalse 失敗。 預期為 'Error': false; 實際為: True」。 因為發生失敗,陣列中 [Error] 之後的字串尚未經過測試。

    Test Explorer window showing the IsFalse assertion failure

  4. 移除您在步驟 1 中加入的字串「Error」後。 重新執行測試,並測試成功通過。

測試程式庫的發行版本

執行程式庫的偵錯組建時,測試都已通過,這次請針對程式庫的發行組建執行一次額外測試。 許多因素 (包括編譯器最佳化) 有時會在偵錯和發行組建之間導致不同的行為。

測試發行組建︰

  1. 在 Visual Studio 工具列中,將組建組態從 [偵錯] 變更為 [發行]

    Visual Studio toolbar with release build highlighted

  2. 在方案總管 中,以滑鼠右鍵按一下 StringLibrary 專案,然後從內容功能表中選取 [組建] 以重新編譯程式庫。

    StringLibrary context menu with build command

  3. 從功能表列中,選擇 [測試]>[執行所有測試] 來執行單元測試。 所有測試皆通過。

針對測試進行偵錯

若您採用 Visual Studio 作為整合式開發環境,可以遵照教學課程:使用 Visual Studio 對 .NET 主控台應用程式偵錯中列出的流程,為單元測試專案使用的程式碼偵錯。 不要啟動 ShowCase 應用程式專案。以滑鼠右鍵按一下 [StringLibraryTests] 專案,然後在操作功能表中選取 [偵錯測試]

Visual Studio 會使用附加的偵錯工具啟動測試專案。 執行會在您新增至測試專案或基礎程式庫程式碼的中斷點停止。

其他資源

下一步

在本教學課程中,您已單元測試類別庫。 您可以將程式庫以套件形式發佈至 NuGet,提供給其他人使用。 如需了解執行方式,請遵循 NuGet 教學課程的內容操作:

若將程式庫以 NuGet 套件的形式發佈,其他人即可安裝並使用。 如需了解執行方式,請遵循 NuGet 教學課程的內容操作:

程式庫不需要以套件的形式散發。 其可以與使用套件的主控台應用程式配套。 若需了解如何發佈主控台應用程式,請參閱本系列先前的教學課程:

本教學課程示範如何新增測試專案至解決方案,以自動化單元測試。

必要條件

建立單元測試專案

單元測試能在開發與發佈期間提供自動化的軟體測試。 MSTest 是三個可供選用的測試架構之一。 另外兩個架構分別是 xUnitnUnit

  1. 啟動 Visual Studio。

  2. 開啟您在 使用 Visual Studio .NET 類別庫時,建立的 ClassLibraryProjects 解決方案。

  3. 將名為 "StringLibraryTest" 的單元測試專案新增到解決方案。

    1. 在 [方案總管] 中以滑鼠右鍵按一下該解決方案,然後選取 [新增]>[新專案]

    2. 在 [新增專案] 頁面上的搜尋方塊中輸入 mstest。 從語言清單中選擇 [C#] 或 [Visual Basic],然後從平台清單中選擇 [所有平台]

    3. 選擇 [MSTest 測試專案] 範本,然後選擇 [下一步]

    4. 在 [設定新專案] 頁面上的 [專案名稱] 方塊中輸入 StringLibraryTest。 接著,選擇 [下一步]

    5. 在 [其他資訊] 頁面上的 [Framework] 方塊中,選取 [.NET 7 (標準字詞支援)]。 接著,選擇 [建立]

  4. Visual Studio 會建立專案,並在程式碼視窗中開啟類別檔案,如以下程式碼所示。 若未顯示您想用的語言,請變更頁面頂端的語言選取器。

    using Microsoft.VisualStudio.TestTools.UnitTesting;
    
    namespace StringLibraryTest
    {
        [TestClass]
        public class UnitTest1
        {
            [TestMethod]
            public void TestMethod1()
            {
            }
        }
    }
    
    Imports Microsoft.VisualStudio.TestTools.UnitTesting
    
    Namespace StringLibraryTest
        <TestClass>
        Public Class UnitTest1
            <TestMethod>
            Sub TestSub()
    
            End Sub
        End Class
    End Namespace
    

    單元測試範本建立的原始程式碼會執行下列動作︰

    執行單元測試時,標記為 [TestClass] 的測試類別中具有 [TestMethod] 標記的每個方法都會自動執行。

新增專案參考

若要讓測試專案使用 StringLibrary 類別,請將 StringLibraryTest 專案中的參考新增至 StringLibrary 專案。

  1. 方案總管 中,以滑鼠右鍵按一下 StringLibraryTest 專案的 [相依性] 節點,然後從內容功能表中選取 [新增專案參考]

  2. [參考管理員] 對話方塊中,展開 [專案] 節點並選取 [StringLibrary] 旁的方塊。 新增參考到 StringLibrary 元件能讓編譯器在編譯 StringLibraryTest 專案時尋找 StringLibrary 方法。

  3. 選取 [確定]。

新增及執行單元測試方法

Visual Studio 執行單元測試時,會執行所有標記 TestMethodAttribute 屬性的類別中標記了 TestClassAttribute 屬性的方法。 測試方法會在發現第一次失敗時,或方法中包含的所有測試完成後結束。

最常見的測試會呼叫 Assert 類別的成員。 許多判斷提示方法都至少包括兩個參數,一個是預期的測試結果,另一個是實際的測試結果。 下表顯示 Assert 類別最常呼叫的方法之一:

Assert 方法 函式
Assert.AreEqual 驗證兩個值或物件相等。 如果值或物件不相等,判斷提示就會失敗。
Assert.AreSame 驗證兩個物件變數參考相同的物件。 如果變數參考不同的物件,判斷提示就會失敗。
Assert.IsFalse 驗證條件為 false。 如果條件為 true,判斷提示就會失敗。
Assert.IsNotNull 驗證物件並非 null。 如果物件為 null,判斷提示就會失敗。

您也可以在測試方法中使用 Assert.ThrowsException 方法,指出預期應擲回的例外狀況類型。 如果指定的例外狀況沒有擲回,測試便會失敗。

在測試 StringLibrary.StartsWithUpper 方法時,您想提供幾個以大寫字元開頭的字串。 您預期此方法在這些情況下傳回 true,因此您可以呼叫 Assert.IsTrue 方法。 同樣地,您想提供幾個開頭不是大寫字元的字串。 您預期此方法在這些情況下傳回 false,因此您可以呼叫 Assert.IsFalse 方法。

既然您的程式庫方法可以處理字串,建議您也一併確認是否能夠成功處理空字串 (String.Empty),亦即不包含任何字元且其 Length 為 0 的有效字串和尚未初始化的 null 字串。 您可以直接呼叫 StartsWithUpper 作為靜態方法,並傳遞單一 String 引數。 也可以在指派給 nullstring 變數上呼叫 StartsWithUpper 作為擴充方法。

您接下來要定義三個方法,每個方法會針對字串陣列中的各個元素呼叫 Assert 方法。 您會呼叫方法多載,以供您指定要在測試失敗後顯示的錯誤訊息。 此訊息會辨識造成失敗的字串。

建立測試方法:

  1. UnitTest1.csUnitTest1.vb 程式碼視窗中,以下列程式碼取代程式碼:

    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using UtilityLibraries;
    
    namespace StringLibraryTest
    {
        [TestClass]
        public class UnitTest1
        {
            [TestMethod]
            public void TestStartsWithUpper()
            {
                // Tests that we expect to return true.
                string[] words = { "Alphabet", "Zebra", "ABC", "Αθήνα", "Москва" };
                foreach (var word in words)
                {
                    bool result = word.StartsWithUpper();
                    Assert.IsTrue(result,
                           string.Format("Expected for '{0}': true; Actual: {1}",
                                         word, result));
                }
            }
    
            [TestMethod]
            public void TestDoesNotStartWithUpper()
            {
                // Tests that we expect to return false.
                string[] words = { "alphabet", "zebra", "abc", "αυτοκινητοβιομηχανία", "государство",
                                   "1234", ".", ";", " " };
                foreach (var word in words)
                {
                    bool result = word.StartsWithUpper();
                    Assert.IsFalse(result,
                           string.Format("Expected for '{0}': false; Actual: {1}",
                                         word, result));
                }
            }
    
            [TestMethod]
            public void DirectCallWithNullOrEmpty()
            {
                // Tests that we expect to return false.
                string?[] words = { string.Empty, null };
                foreach (var word in words)
                {
                    bool result = StringLibrary.StartsWithUpper(word);
                    Assert.IsFalse(result,
                           string.Format("Expected for '{0}': false; Actual: {1}",
                                         word == null ? "<null>" : word, result));
                }
            }
        }
    }
    
    Imports Microsoft.VisualStudio.TestTools.UnitTesting
    Imports UtilityLibraries
    
    Namespace StringLibraryTest
        <TestClass>
        Public Class UnitTest1
            <TestMethod>
            Public Sub TestStartsWithUpper()
                ' Tests that we expect to return true.
                Dim words() As String = {"Alphabet", "Zebra", "ABC", "Αθήνα", "Москва"}
                For Each word In words
                    Dim result As Boolean = word.StartsWithUpper()
                    Assert.IsTrue(result,
                           $"Expected for '{word}': true; Actual: {result}")
                Next
            End Sub
    
            <TestMethod>
            Public Sub TestDoesNotStartWithUpper()
                ' Tests that we expect to return false.
                Dim words() As String = {"alphabet", "zebra", "abc", "αυτοκινητοβιομηχανία", "государство",
                                   "1234", ".", ";", " "}
                For Each word In words
                    Dim result As Boolean = word.StartsWithUpper()
                    Assert.IsFalse(result,
                           $"Expected for '{word}': false; Actual: {result}")
                Next
            End Sub
    
            <TestMethod>
            Public Sub DirectCallWithNullOrEmpty()
                ' Tests that we expect to return false.
                Dim words() As String = {String.Empty, Nothing}
                For Each word In words
                    Dim result As Boolean = StringLibrary.StartsWithUpper(word)
                    Assert.IsFalse(result,
                           $"Expected for '{If(word Is Nothing, "<null>", word)}': false; Actual: {result}")
                Next
            End Sub
        End Class
    End Namespace
    

    TestStartsWithUpper 方法中的大寫字元測試包括希臘文大寫字母 alpha (U+0391) 和斯拉夫文大寫字母 EM (U+041C)。 TestDoesNotStartWithUpper 方法中的小寫字元測試包括希臘文小寫字母 alpha (U+03B1) 和斯拉夫文小寫字母 Ghe (U+0433)。

  2. 在功能表列上,選取 [檔案]>[另存 UnitTest1.cs 為] 或 [檔案]>[另存 UnitTest1.vb 為]。 在 [另存新檔] 對話方塊中,選擇 [儲存] 按鈕旁的箭號,然後選擇 [以編碼方式儲存]*。

    Visual Studio Save File As dialog

  3. [確認另存新檔] 對話方塊中,選擇 [是] 按鈕以儲存檔案。

  4. [進階儲存選項] 對話方塊的,選取 [編碼] 下拉式清單中選擇 [Unicode (UTF-8 有簽章) - 字碼頁 65001],然後選擇 [確定]

    Visual Studio Advanced Save Options dialog

    如果您無法將您的原始程式碼儲存為 UTF8 編碼檔案,Visual Studio 可能會將它儲存為 ASCII 檔案。 若發生這種情況,執行階段便無法正確解碼 ASCII 範圍之外的 UTF8 字元,因此測試結果會有錯誤。

  5. 在功能表列上,選擇 [測試]>[執行所有測試]。 如果 [測試總管] 視窗未開啟,請藉由選取 [測試]>[測試總管]加以開啟。 這三項測試都會列在 [通過的測試] 區段中,而 [摘要] 區段則報告測試回合的結果。

    Test Explorer window with passing tests

處理測試失敗

如果您正在執行測試驅動開發 (TDD),請先轉寫測試,並在第一次執行測試時失敗。 接著,在應用程式中新增可以使測試成功的程式碼。 在本教學課程中,您先撰寫應用程式程式碼,才建立用於驗證的測試,因此測試尚未失敗過。 為了驗證在測試預計會失敗的清況下,測試會確實失敗,請在測試輸入中加入不無效值。

  1. 修改 TestDoesNotStartWithUpper 方法中的 words 陣列,以包含字串「錯誤」。 您不需要儲存檔案,因為當組建方案以執行測試時,Visual Studio 會自動儲存開啟的檔案。

    string[] words = { "alphabet", "Error", "zebra", "abc", "αυτοκινητοβιομηχανία", "государство",
                       "1234", ".", ";", " " };
    
    Dim words() As String = { "alphabet", "Error", "zebra", "abc", "αυτοκινητοβιομηχανία", "государство",
                       "1234", ".", ";", " " }
    
    
  2. 從功能表列中,選取 [測試]>[執行所有測試] 來執行測試。 [測試總管] 視窗表示兩個測試成功,而且有一項失敗。

    Test Explorer window with failing tests

  3. 選取失敗的測試,TestDoesNotStartWith

    [測試總管] 下方的窗格會顯示判斷提示產生的訊息:「Assert.IsFalse 失敗。 預期為 'Error': false; 實際為: True」。 因為發生失敗,陣列中 [Error] 之後的字串尚未經過測試。

    Test Explorer window showing the IsFalse assertion failure

  4. 移除您在步驟 1 中加入的字串「Error」後。 重新執行測試,並測試成功通過。

測試程式庫的發行版本

執行程式庫的偵錯組建時,測試都已通過,這次請針對程式庫的發行組建執行一次額外測試。 許多因素 (包括編譯器最佳化) 有時會在偵錯和發行組建之間導致不同的行為。

測試發行組建︰

  1. 在 Visual Studio 工具列中,將組建組態從 [偵錯] 變更為 [發行]

    Visual Studio toolbar with release build highlighted

  2. 在方案總管 中,以滑鼠右鍵按一下 StringLibrary 專案,然後從內容功能表中選取 [組建] 以重新編譯程式庫。

    StringLibrary context menu with build command

  3. 從功能表列中,選擇 [測試]>[執行所有測試] 來執行單元測試。 所有測試皆通過。

針對測試進行偵錯

若您採用 Visual Studio 作為整合式開發環境,可以遵照教學課程:使用 Visual Studio 對 .NET 主控台應用程式偵錯中列出的流程,為單元測試專案使用的程式碼偵錯。 不要啟動 ShowCase 應用程式專案。以滑鼠右鍵按一下 [StringLibraryTests] 專案,然後在操作功能表中選取 [偵錯測試]

Visual Studio 會使用附加的偵錯工具啟動測試專案。 執行會在您新增至測試專案或基礎程式庫程式碼的中斷點停止。

其他資源

下一步

在本教學課程中,您已單元測試類別庫。 您可以將程式庫以套件形式發佈至 NuGet,提供給其他人使用。 如需了解執行方式,請遵循 NuGet 教學課程的內容操作:

若將程式庫以 NuGet 套件的形式發佈,其他人即可安裝並使用。 如需了解執行方式,請遵循 NuGet 教學課程的內容操作:

程式庫不需要以套件的形式散發。 其可以與使用套件的主控台應用程式配套。 若需了解如何發佈主控台應用程式,請參閱本系列先前的教學課程:

本教學課程示範如何新增測試專案至解決方案,以自動化單元測試。

必要條件

建立單元測試專案

單元測試能在開發與發佈期間提供自動化的軟體測試。 MSTest 是三個可供選用的測試架構之一。 另外兩個架構分別是 xUnitnUnit

  1. 啟動 Visual Studio。

  2. 開啟您在 使用 Visual Studio .NET 類別庫時,建立的 ClassLibraryProjects 解決方案。

  3. 將名為 "StringLibraryTest" 的單元測試專案新增到解決方案。

    1. 在 [方案總管] 中以滑鼠右鍵按一下該解決方案,然後選取 [新增]>[新專案]

    2. 在 [新增專案] 頁面上的搜尋方塊中輸入 mstest。 從語言清單中選擇 [C#] 或 [Visual Basic],然後從平台清單中選擇 [所有平台]

    3. 選擇 [MSTest 測試專案] 範本,然後選擇 [下一步]

    4. 在 [設定新專案] 頁面上的 [專案名稱] 方塊中輸入 StringLibraryTest。 接著,選擇 [下一步]

    5. 在 [其他資訊] 頁面上的 [Framework] 方塊中,選取 [.NET 6 (長期字詞支援)]。 接著,選擇 [建立]

  4. Visual Studio 會建立專案,並在程式碼視窗中開啟類別檔案,如以下程式碼所示。 若未顯示您想用的語言,請變更頁面頂端的語言選取器。

    using Microsoft.VisualStudio.TestTools.UnitTesting;
    
    namespace StringLibraryTest
    {
        [TestClass]
        public class UnitTest1
        {
            [TestMethod]
            public void TestMethod1()
            {
            }
        }
    }
    
    Imports Microsoft.VisualStudio.TestTools.UnitTesting
    
    Namespace StringLibraryTest
        <TestClass>
        Public Class UnitTest1
            <TestMethod>
            Sub TestSub()
    
            End Sub
        End Class
    End Namespace
    

    單元測試範本建立的原始程式碼會執行下列動作︰

    執行單元測試時,標記為 [TestClass] 的測試類別中具有 [TestMethod] 標記的每個方法都會自動執行。

新增專案參考

若要讓測試專案使用 StringLibrary 類別,請將 StringLibraryTest 專案中的參考新增至 StringLibrary 專案。

  1. 方案總管 中,以滑鼠右鍵按一下 StringLibraryTest 專案的 [相依性] 節點,然後從內容功能表中選取 [新增專案參考]

  2. [參考管理員] 對話方塊中,展開 [專案] 節點並選取 [StringLibrary] 旁的方塊。 新增參考到 StringLibrary 元件能讓編譯器在編譯 StringLibraryTest 專案時尋找 StringLibrary 方法。

  3. 選取 [確定]。

新增及執行單元測試方法

Visual Studio 執行單元測試時,會執行所有標記 TestMethodAttribute 屬性的類別中標記了 TestClassAttribute 屬性的方法。 測試方法會在發現第一次失敗時,或方法中包含的所有測試完成後結束。

最常見的測試會呼叫 Assert 類別的成員。 許多判斷提示方法都至少包括兩個參數,一個是預期的測試結果,另一個是實際的測試結果。 下表顯示 Assert 類別最常呼叫的方法之一:

Assert 方法 函式
Assert.AreEqual 驗證兩個值或物件相等。 如果值或物件不相等,判斷提示就會失敗。
Assert.AreSame 驗證兩個物件變數參考相同的物件。 如果變數參考不同的物件,判斷提示就會失敗。
Assert.IsFalse 驗證條件為 false。 如果條件為 true,判斷提示就會失敗。
Assert.IsNotNull 驗證物件並非 null。 如果物件為 null,判斷提示就會失敗。

您也可以在測試方法中使用 Assert.ThrowsException 方法,指出預期應擲回的例外狀況類型。 如果指定的例外狀況沒有擲回,測試便會失敗。

在測試 StringLibrary.StartsWithUpper 方法時,您想提供幾個以大寫字元開頭的字串。 您預期此方法在這些情況下傳回 true,因此您可以呼叫 Assert.IsTrue 方法。 同樣地,您想提供幾個開頭不是大寫字元的字串。 您預期此方法在這些情況下傳回 false,因此您可以呼叫 Assert.IsFalse 方法。

既然您的程式庫方法可以處理字串,建議您也一併確認是否能夠成功處理空字串 (String.Empty),亦即不包含任何字元且其 Length 為 0 的有效字串和尚未初始化的 null 字串。 您可以直接呼叫 StartsWithUpper 作為靜態方法,並傳遞單一 String 引數。 也可以在指派給 nullstring 變數上呼叫 StartsWithUpper 作為擴充方法。

您接下來要定義三個方法,每個方法會針對字串陣列中的各個元素呼叫 Assert 方法。 您會呼叫方法多載,以供您指定要在測試失敗後顯示的錯誤訊息。 此訊息會辨識造成失敗的字串。

建立測試方法:

  1. UnitTest1.csUnitTest1.vb 程式碼視窗中,以下列程式碼取代程式碼:

    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using UtilityLibraries;
    
    namespace StringLibraryTest
    {
        [TestClass]
        public class UnitTest1
        {
            [TestMethod]
            public void TestStartsWithUpper()
            {
                // Tests that we expect to return true.
                string[] words = { "Alphabet", "Zebra", "ABC", "Αθήνα", "Москва" };
                foreach (var word in words)
                {
                    bool result = word.StartsWithUpper();
                    Assert.IsTrue(result,
                           string.Format("Expected for '{0}': true; Actual: {1}",
                                         word, result));
                }
            }
    
            [TestMethod]
            public void TestDoesNotStartWithUpper()
            {
                // Tests that we expect to return false.
                string[] words = { "alphabet", "zebra", "abc", "αυτοκινητοβιομηχανία", "государство",
                                   "1234", ".", ";", " " };
                foreach (var word in words)
                {
                    bool result = word.StartsWithUpper();
                    Assert.IsFalse(result,
                           string.Format("Expected for '{0}': false; Actual: {1}",
                                         word, result));
                }
            }
    
            [TestMethod]
            public void DirectCallWithNullOrEmpty()
            {
                // Tests that we expect to return false.
                string?[] words = { string.Empty, null };
                foreach (var word in words)
                {
                    bool result = StringLibrary.StartsWithUpper(word);
                    Assert.IsFalse(result,
                           string.Format("Expected for '{0}': false; Actual: {1}",
                                         word == null ? "<null>" : word, result));
                }
            }
        }
    }
    
    Imports Microsoft.VisualStudio.TestTools.UnitTesting
    Imports UtilityLibraries
    
    Namespace StringLibraryTest
        <TestClass>
        Public Class UnitTest1
            <TestMethod>
            Public Sub TestStartsWithUpper()
                ' Tests that we expect to return true.
                Dim words() As String = {"Alphabet", "Zebra", "ABC", "Αθήνα", "Москва"}
                For Each word In words
                    Dim result As Boolean = word.StartsWithUpper()
                    Assert.IsTrue(result,
                           $"Expected for '{word}': true; Actual: {result}")
                Next
            End Sub
    
            <TestMethod>
            Public Sub TestDoesNotStartWithUpper()
                ' Tests that we expect to return false.
                Dim words() As String = {"alphabet", "zebra", "abc", "αυτοκινητοβιομηχανία", "государство",
                                   "1234", ".", ";", " "}
                For Each word In words
                    Dim result As Boolean = word.StartsWithUpper()
                    Assert.IsFalse(result,
                           $"Expected for '{word}': false; Actual: {result}")
                Next
            End Sub
    
            <TestMethod>
            Public Sub DirectCallWithNullOrEmpty()
                ' Tests that we expect to return false.
                Dim words() As String = {String.Empty, Nothing}
                For Each word In words
                    Dim result As Boolean = StringLibrary.StartsWithUpper(word)
                    Assert.IsFalse(result,
                           $"Expected for '{If(word Is Nothing, "<null>", word)}': false; Actual: {result}")
                Next
            End Sub
        End Class
    End Namespace
    

    TestStartsWithUpper 方法中的大寫字元測試包括希臘文大寫字母 alpha (U+0391) 和斯拉夫文大寫字母 EM (U+041C)。 TestDoesNotStartWithUpper 方法中的小寫字元測試包括希臘文小寫字母 alpha (U+03B1) 和斯拉夫文小寫字母 Ghe (U+0433)。

  2. 在功能表列上,選取 [檔案]>[另存 UnitTest1.cs 為] 或 [檔案]>[另存 UnitTest1.vb 為]。 在 [另存新檔] 對話方塊中,選擇 [儲存] 按鈕旁的箭號,然後選擇 [以編碼方式儲存]*。

    Visual Studio Save File As dialog

  3. [確認另存新檔] 對話方塊中,選擇 [是] 按鈕以儲存檔案。

  4. [進階儲存選項] 對話方塊的,選取 [編碼] 下拉式清單中選擇 [Unicode (UTF-8 有簽章) - 字碼頁 65001],然後選擇 [確定]

    Visual Studio Advanced Save Options dialog

    如果您無法將您的原始程式碼儲存為 UTF8 編碼檔案,Visual Studio 可能會將它儲存為 ASCII 檔案。 若發生這種情況,執行階段便無法正確解碼 ASCII 範圍之外的 UTF8 字元,因此測試結果會有錯誤。

  5. 在功能表列上,選擇 [測試]>[執行所有測試]。 如果 [測試總管] 視窗未開啟,請藉由選取 [測試]>[測試總管]加以開啟。 這三項測試都會列在 [通過的測試] 區段中,而 [摘要] 區段則報告測試回合的結果。

    Test Explorer window with passing tests

處理測試失敗

如果您正在執行測試驅動開發 (TDD),請先轉寫測試,並在第一次執行測試時失敗。 接著,在應用程式中新增可以使測試成功的程式碼。 在本教學課程中,您先撰寫應用程式程式碼,才建立用於驗證的測試,因此測試尚未失敗過。 為了驗證在測試預計會失敗的清況下,測試會確實失敗,請在測試輸入中加入不無效值。

  1. 修改 TestDoesNotStartWithUpper 方法中的 words 陣列,以包含字串「錯誤」。 您不需要儲存檔案,因為當組建方案以執行測試時,Visual Studio 會自動儲存開啟的檔案。

    string[] words = { "alphabet", "Error", "zebra", "abc", "αυτοκινητοβιομηχανία", "государство",
                       "1234", ".", ";", " " };
    
    Dim words() As String = { "alphabet", "Error", "zebra", "abc", "αυτοκινητοβιομηχανία", "государство",
                       "1234", ".", ";", " " }
    
    
  2. 從功能表列中,選取 [測試]>[執行所有測試] 來執行測試。 [測試總管] 視窗表示兩個測試成功,而且有一項失敗。

    Test Explorer window with failing tests

  3. 選取失敗的測試,TestDoesNotStartWith

    [測試總管] 下方的窗格會顯示判斷提示產生的訊息:「Assert.IsFalse 失敗。 預期為 'Error': false; 實際為: True」。 因為發生失敗,陣列中 [Error] 之後的字串尚未經過測試。

    Test Explorer window showing the IsFalse assertion failure

  4. 移除您在步驟 1 中加入的字串「Error」後。 重新執行測試,並測試成功通過。

測試程式庫的發行版本

執行程式庫的偵錯組建時,測試都已通過,這次請針對程式庫的發行組建執行一次額外測試。 許多因素 (包括編譯器最佳化) 有時會在偵錯和發行組建之間導致不同的行為。

測試發行組建︰

  1. 在 Visual Studio 工具列中,將組建組態從 [偵錯] 變更為 [發行]

    Visual Studio toolbar with release build highlighted

  2. 在方案總管 中,以滑鼠右鍵按一下 StringLibrary 專案,然後從內容功能表中選取 [組建] 以重新編譯程式庫。

    StringLibrary context menu with build command

  3. 從功能表列中,選擇 [測試]>[執行所有測試] 來執行單元測試。 所有測試皆通過。

針對測試進行偵錯

若您採用 Visual Studio 作為整合式開發環境,可以遵照教學課程:使用 Visual Studio 對 .NET 主控台應用程式偵錯中列出的流程,為單元測試專案使用的程式碼偵錯。 不要啟動 ShowCase 應用程式專案。以滑鼠右鍵按一下 [StringLibraryTests] 專案,然後在操作功能表中選取 [偵錯測試]

Visual Studio 會使用附加的偵錯工具啟動測試專案。 執行會在您新增至測試專案或基礎程式庫程式碼的中斷點停止。

其他資源

下一步

在本教學課程中,您已單元測試類別庫。 您可以將程式庫以套件形式發佈至 NuGet,提供給其他人使用。 如需了解執行方式,請遵循 NuGet 教學課程的內容操作:

若將程式庫以 NuGet 套件的形式發佈,其他人即可安裝並使用。 如需了解執行方式,請遵循 NuGet 教學課程的內容操作:

程式庫不需要以套件的形式散發。 其可以與使用套件的主控台應用程式配套。 若需了解如何發佈主控台應用程式,請參閱本系列先前的教學課程: