教學課程: 使用 Visual Studio 搭配 .NET 測試 .NET 類別庫
本教學課程示範如何新增測試專案至解決方案,以自動化單元測試。
必要條件
- 本教學課程適用於使用 Visual Studio 建立 .NET 類別庫 時所建立的解決方案。
建立單元測試專案
單元測試能在開發與發佈期間提供自動化的軟體測試。 MSTest 是三個可供選用的測試架構之一。 另外兩個架構分別是 xUnit 與 nUnit。
啟動 Visual Studio。
開啟您在 使用 Visual Studio .NET 類別庫時,建立的
ClassLibraryProjects
解決方案。將名為 "StringLibraryTest" 的單元測試專案新增到解決方案。
在 [方案總管] 中以滑鼠右鍵按一下該解決方案,然後選取 [新增]>[新專案]。
在 [新增專案] 頁面上的搜尋方塊中輸入 mstest。 從語言清單中選擇 [C#] 或 [Visual Basic],然後從平台清單中選擇 [所有平台]。
選擇 [MSTest 測試專案] 範本,然後選擇 [下一步]。
在 [設定新專案] 頁面上的 [專案名稱] 方塊中輸入 StringLibraryTest。 接著,選擇 [下一步]。
在 [其他資訊] 頁面上的 [Framework] 方塊中,選取 [.NET 8 (預覽)]。 接著,選擇 [建立]。
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
單元測試範本建立的原始程式碼會執行下列動作︰
- 它會匯入 Microsoft.VisualStudio.TestTools.UnitTesting 命名空間,其中包含用於單元測試的類型。 在 C# 中,命名空間是透過 GlobalUsings.cs 中的
global using
指示詞彙入。 - 它會將 TestClassAttribute 屬性套用至
UnitTest1
類別。 - 其會套用 TestMethodAttribute 屬性,以定義
TestMethod1
(C#) 或TestSub
(Visual Basic)。
執行單元測試時,標記為 [TestClass] 的測試類別中具有 [TestMethod] 標記的每個方法都會自動執行。
- 它會匯入 Microsoft.VisualStudio.TestTools.UnitTesting 命名空間,其中包含用於單元測試的類型。 在 C# 中,命名空間是透過 GlobalUsings.cs 中的
新增專案參考
若要讓測試專案使用 StringLibrary
類別,請將 StringLibraryTest 專案中的參考新增至 StringLibrary
專案。
在 方案總管 中,以滑鼠右鍵按一下 StringLibraryTest 專案的 [相依性] 節點,然後從內容功能表中選取 [新增專案參考]。
在 [參考管理員] 對話方塊中,展開 [專案] 節點並選取 [StringLibrary] 旁的方塊。 新增參考到
StringLibrary
元件能讓編譯器在編譯 StringLibraryTest 專案時尋找 StringLibrary 方法。選取 [確定]。
新增及執行單元測試方法
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 引數。 也可以在指派給 null
的 string
變數上呼叫 StartsWithUpper
作為擴充方法。
您接下來要定義三個方法,每個方法會針對字串陣列中的各個元素呼叫 Assert 方法。 您會呼叫方法多載,以供您指定要在測試失敗後顯示的錯誤訊息。 此訊息會辨識造成失敗的字串。
建立測試方法:
在 UnitTest1.cs 或 UnitTest1.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)。在功能表列上,選取 [檔案]>[另存 UnitTest1.cs 為] 或 [檔案]>[另存 UnitTest1.vb 為]。 在 [另存新檔] 對話方塊中,選擇 [儲存] 按鈕旁的箭號,然後選擇 [以編碼方式儲存]*。
在 [確認另存新檔] 對話方塊中,選擇 [是] 按鈕以儲存檔案。
在 [進階儲存選項] 對話方塊的,選取 [編碼] 下拉式清單中選擇 [Unicode (UTF-8 有簽章) - 字碼頁 65001],然後選擇 [確定]。
如果您無法將您的原始程式碼儲存為 UTF8 編碼檔案,Visual Studio 可能會將它儲存為 ASCII 檔案。 若發生這種情況,執行階段便無法正確解碼 ASCII 範圍之外的 UTF8 字元,因此測試結果會有錯誤。
在功能表列上,選擇 [測試]>[執行所有測試]。 如果 [測試總管] 視窗未開啟,請藉由選取 [測試]>[測試總管]加以開啟。 這三項測試都會列在 [通過的測試] 區段中,而 [摘要] 區段則報告測試回合的結果。
處理測試失敗
如果您正在執行測試驅動開發 (TDD),請先轉寫測試,並在第一次執行測試時失敗。 接著,在應用程式中新增可以使測試成功的程式碼。 在本教學課程中,您先撰寫應用程式程式碼,才建立用於驗證的測試,因此測試尚未失敗過。 為了驗證在測試預計會失敗的清況下,測試會確實失敗,請在測試輸入中加入不無效值。
修改
TestDoesNotStartWithUpper
方法中的words
陣列,以包含字串「錯誤」。 您不需要儲存檔案,因為當組建方案以執行測試時,Visual Studio 會自動儲存開啟的檔案。string[] words = { "alphabet", "Error", "zebra", "abc", "αυτοκινητοβιομηχανία", "государство", "1234", ".", ";", " " };
Dim words() As String = { "alphabet", "Error", "zebra", "abc", "αυτοκινητοβιομηχανία", "государство", "1234", ".", ";", " " }
從功能表列中,選取 [測試]>[執行所有測試] 來執行測試。 [測試總管] 視窗表示兩個測試成功,而且有一項失敗。
選取失敗的測試,
TestDoesNotStartWith
。[測試總管] 下方的窗格會顯示判斷提示產生的訊息:「Assert.IsFalse 失敗。 預期為 'Error': false; 實際為: True」。 因為發生失敗,陣列中 [Error] 之後的字串尚未經過測試。
移除您在步驟 1 中加入的字串「Error」後。 重新執行測試,並測試成功通過。
測試程式庫的發行版本
執行程式庫的偵錯組建時,測試都已通過,這次請針對程式庫的發行組建執行一次額外測試。 許多因素 (包括編譯器最佳化) 有時會在偵錯和發行組建之間導致不同的行為。
測試發行組建︰
在 Visual Studio 工具列中,將組建組態從 [偵錯] 變更為 [發行]。
在方案總管 中,以滑鼠右鍵按一下 StringLibrary 專案,然後從內容功能表中選取 [組建] 以重新編譯程式庫。
從功能表列中,選擇 [測試]>[執行所有測試] 來執行單元測試。 所有測試皆通過。
針對測試進行偵錯
若您採用 Visual Studio 作為整合式開發環境,可以遵照教學課程:使用 Visual Studio 對 .NET 主控台應用程式偵錯中列出的流程,為單元測試專案使用的程式碼偵錯。 不要啟動 ShowCase 應用程式專案。以滑鼠右鍵按一下 [StringLibraryTests] 專案,然後在操作功能表中選取 [偵錯測試]。
Visual Studio 會使用附加的偵錯工具啟動測試專案。 執行會在您新增至測試專案或基礎程式庫程式碼的中斷點停止。
其他資源
下一步
在本教學課程中,您已單元測試類別庫。 您可以將程式庫以套件形式發佈至 NuGet,提供給其他人使用。 如需了解執行方式,請遵循 NuGet 教學課程的內容操作:
若將程式庫以 NuGet 套件的形式發佈,其他人即可安裝並使用。 如需了解執行方式,請遵循 NuGet 教學課程的內容操作:
程式庫不需要以套件的形式散發。 其可以與使用套件的主控台應用程式配套。 若需了解如何發佈主控台應用程式,請參閱本系列先前的教學課程:
本教學課程示範如何新增測試專案至解決方案,以自動化單元測試。
必要條件
- 本教學課程適用於使用 Visual Studio 建立 .NET 類別庫 時所建立的解決方案。
建立單元測試專案
單元測試能在開發與發佈期間提供自動化的軟體測試。 MSTest 是三個可供選用的測試架構之一。 另外兩個架構分別是 xUnit 與 nUnit。
啟動 Visual Studio。
開啟您在 使用 Visual Studio .NET 類別庫時,建立的
ClassLibraryProjects
解決方案。將名為 "StringLibraryTest" 的單元測試專案新增到解決方案。
在 [方案總管] 中以滑鼠右鍵按一下該解決方案,然後選取 [新增]>[新專案]。
在 [新增專案] 頁面上的搜尋方塊中輸入 mstest。 從語言清單中選擇 [C#] 或 [Visual Basic],然後從平台清單中選擇 [所有平台]。
選擇 [MSTest 測試專案] 範本,然後選擇 [下一步]。
在 [設定新專案] 頁面上的 [專案名稱] 方塊中輸入 StringLibraryTest。 接著,選擇 [下一步]。
在 [其他資訊] 頁面上的 [Framework] 方塊中,選取 [.NET 7 (標準字詞支援)]。 接著,選擇 [建立]。
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
單元測試範本建立的原始程式碼會執行下列動作︰
- 它會匯入 Microsoft.VisualStudio.TestTools.UnitTesting 命名空間,其中包含用於單元測試的類型。
- 它會將 TestClassAttribute 屬性套用至
UnitTest1
類別。 - 其會套用 TestMethodAttribute 屬性,以定義
TestMethod1
(C#) 或TestSub
(Visual Basic)。
執行單元測試時,標記為 [TestClass] 的測試類別中具有 [TestMethod] 標記的每個方法都會自動執行。
新增專案參考
若要讓測試專案使用 StringLibrary
類別,請將 StringLibraryTest 專案中的參考新增至 StringLibrary
專案。
在 方案總管 中,以滑鼠右鍵按一下 StringLibraryTest 專案的 [相依性] 節點,然後從內容功能表中選取 [新增專案參考]。
在 [參考管理員] 對話方塊中,展開 [專案] 節點並選取 [StringLibrary] 旁的方塊。 新增參考到
StringLibrary
元件能讓編譯器在編譯 StringLibraryTest 專案時尋找 StringLibrary 方法。選取 [確定]。
新增及執行單元測試方法
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 引數。 也可以在指派給 null
的 string
變數上呼叫 StartsWithUpper
作為擴充方法。
您接下來要定義三個方法,每個方法會針對字串陣列中的各個元素呼叫 Assert 方法。 您會呼叫方法多載,以供您指定要在測試失敗後顯示的錯誤訊息。 此訊息會辨識造成失敗的字串。
建立測試方法:
在 UnitTest1.cs 或 UnitTest1.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)。在功能表列上,選取 [檔案]>[另存 UnitTest1.cs 為] 或 [檔案]>[另存 UnitTest1.vb 為]。 在 [另存新檔] 對話方塊中,選擇 [儲存] 按鈕旁的箭號,然後選擇 [以編碼方式儲存]*。
在 [確認另存新檔] 對話方塊中,選擇 [是] 按鈕以儲存檔案。
在 [進階儲存選項] 對話方塊的,選取 [編碼] 下拉式清單中選擇 [Unicode (UTF-8 有簽章) - 字碼頁 65001],然後選擇 [確定]。
如果您無法將您的原始程式碼儲存為 UTF8 編碼檔案,Visual Studio 可能會將它儲存為 ASCII 檔案。 若發生這種情況,執行階段便無法正確解碼 ASCII 範圍之外的 UTF8 字元,因此測試結果會有錯誤。
在功能表列上,選擇 [測試]>[執行所有測試]。 如果 [測試總管] 視窗未開啟,請藉由選取 [測試]>[測試總管]加以開啟。 這三項測試都會列在 [通過的測試] 區段中,而 [摘要] 區段則報告測試回合的結果。
處理測試失敗
如果您正在執行測試驅動開發 (TDD),請先轉寫測試,並在第一次執行測試時失敗。 接著,在應用程式中新增可以使測試成功的程式碼。 在本教學課程中,您先撰寫應用程式程式碼,才建立用於驗證的測試,因此測試尚未失敗過。 為了驗證在測試預計會失敗的清況下,測試會確實失敗,請在測試輸入中加入不無效值。
修改
TestDoesNotStartWithUpper
方法中的words
陣列,以包含字串「錯誤」。 您不需要儲存檔案,因為當組建方案以執行測試時,Visual Studio 會自動儲存開啟的檔案。string[] words = { "alphabet", "Error", "zebra", "abc", "αυτοκινητοβιομηχανία", "государство", "1234", ".", ";", " " };
Dim words() As String = { "alphabet", "Error", "zebra", "abc", "αυτοκινητοβιομηχανία", "государство", "1234", ".", ";", " " }
從功能表列中,選取 [測試]>[執行所有測試] 來執行測試。 [測試總管] 視窗表示兩個測試成功,而且有一項失敗。
選取失敗的測試,
TestDoesNotStartWith
。[測試總管] 下方的窗格會顯示判斷提示產生的訊息:「Assert.IsFalse 失敗。 預期為 'Error': false; 實際為: True」。 因為發生失敗,陣列中 [Error] 之後的字串尚未經過測試。
移除您在步驟 1 中加入的字串「Error」後。 重新執行測試,並測試成功通過。
測試程式庫的發行版本
執行程式庫的偵錯組建時,測試都已通過,這次請針對程式庫的發行組建執行一次額外測試。 許多因素 (包括編譯器最佳化) 有時會在偵錯和發行組建之間導致不同的行為。
測試發行組建︰
在 Visual Studio 工具列中,將組建組態從 [偵錯] 變更為 [發行]。
在方案總管 中,以滑鼠右鍵按一下 StringLibrary 專案,然後從內容功能表中選取 [組建] 以重新編譯程式庫。
從功能表列中,選擇 [測試]>[執行所有測試] 來執行單元測試。 所有測試皆通過。
針對測試進行偵錯
若您採用 Visual Studio 作為整合式開發環境,可以遵照教學課程:使用 Visual Studio 對 .NET 主控台應用程式偵錯中列出的流程,為單元測試專案使用的程式碼偵錯。 不要啟動 ShowCase 應用程式專案。以滑鼠右鍵按一下 [StringLibraryTests] 專案,然後在操作功能表中選取 [偵錯測試]。
Visual Studio 會使用附加的偵錯工具啟動測試專案。 執行會在您新增至測試專案或基礎程式庫程式碼的中斷點停止。
其他資源
下一步
在本教學課程中,您已單元測試類別庫。 您可以將程式庫以套件形式發佈至 NuGet,提供給其他人使用。 如需了解執行方式,請遵循 NuGet 教學課程的內容操作:
若將程式庫以 NuGet 套件的形式發佈,其他人即可安裝並使用。 如需了解執行方式,請遵循 NuGet 教學課程的內容操作:
程式庫不需要以套件的形式散發。 其可以與使用套件的主控台應用程式配套。 若需了解如何發佈主控台應用程式,請參閱本系列先前的教學課程:
本教學課程示範如何新增測試專案至解決方案,以自動化單元測試。
必要條件
- 本教學課程適用於使用 Visual Studio 建立 .NET 類別庫 時所建立的解決方案。
建立單元測試專案
單元測試能在開發與發佈期間提供自動化的軟體測試。 MSTest 是三個可供選用的測試架構之一。 另外兩個架構分別是 xUnit 與 nUnit。
啟動 Visual Studio。
開啟您在 使用 Visual Studio .NET 類別庫時,建立的
ClassLibraryProjects
解決方案。將名為 "StringLibraryTest" 的單元測試專案新增到解決方案。
在 [方案總管] 中以滑鼠右鍵按一下該解決方案,然後選取 [新增]>[新專案]。
在 [新增專案] 頁面上的搜尋方塊中輸入 mstest。 從語言清單中選擇 [C#] 或 [Visual Basic],然後從平台清單中選擇 [所有平台]。
選擇 [MSTest 測試專案] 範本,然後選擇 [下一步]。
在 [設定新專案] 頁面上的 [專案名稱] 方塊中輸入 StringLibraryTest。 接著,選擇 [下一步]。
在 [其他資訊] 頁面上的 [Framework] 方塊中,選取 [.NET 6 (長期字詞支援)]。 接著,選擇 [建立]。
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
單元測試範本建立的原始程式碼會執行下列動作︰
- 它會匯入 Microsoft.VisualStudio.TestTools.UnitTesting 命名空間,其中包含用於單元測試的類型。
- 它會將 TestClassAttribute 屬性套用至
UnitTest1
類別。 - 其會套用 TestMethodAttribute 屬性,以定義
TestMethod1
(C#) 或TestSub
(Visual Basic)。
執行單元測試時,標記為 [TestClass] 的測試類別中具有 [TestMethod] 標記的每個方法都會自動執行。
新增專案參考
若要讓測試專案使用 StringLibrary
類別,請將 StringLibraryTest 專案中的參考新增至 StringLibrary
專案。
在 方案總管 中,以滑鼠右鍵按一下 StringLibraryTest 專案的 [相依性] 節點,然後從內容功能表中選取 [新增專案參考]。
在 [參考管理員] 對話方塊中,展開 [專案] 節點並選取 [StringLibrary] 旁的方塊。 新增參考到
StringLibrary
元件能讓編譯器在編譯 StringLibraryTest 專案時尋找 StringLibrary 方法。選取 [確定]。
新增及執行單元測試方法
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 引數。 也可以在指派給 null
的 string
變數上呼叫 StartsWithUpper
作為擴充方法。
您接下來要定義三個方法,每個方法會針對字串陣列中的各個元素呼叫 Assert 方法。 您會呼叫方法多載,以供您指定要在測試失敗後顯示的錯誤訊息。 此訊息會辨識造成失敗的字串。
建立測試方法:
在 UnitTest1.cs 或 UnitTest1.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)。在功能表列上,選取 [檔案]>[另存 UnitTest1.cs 為] 或 [檔案]>[另存 UnitTest1.vb 為]。 在 [另存新檔] 對話方塊中,選擇 [儲存] 按鈕旁的箭號,然後選擇 [以編碼方式儲存]*。
在 [確認另存新檔] 對話方塊中,選擇 [是] 按鈕以儲存檔案。
在 [進階儲存選項] 對話方塊的,選取 [編碼] 下拉式清單中選擇 [Unicode (UTF-8 有簽章) - 字碼頁 65001],然後選擇 [確定]。
如果您無法將您的原始程式碼儲存為 UTF8 編碼檔案,Visual Studio 可能會將它儲存為 ASCII 檔案。 若發生這種情況,執行階段便無法正確解碼 ASCII 範圍之外的 UTF8 字元,因此測試結果會有錯誤。
在功能表列上,選擇 [測試]>[執行所有測試]。 如果 [測試總管] 視窗未開啟,請藉由選取 [測試]>[測試總管]加以開啟。 這三項測試都會列在 [通過的測試] 區段中,而 [摘要] 區段則報告測試回合的結果。
處理測試失敗
如果您正在執行測試驅動開發 (TDD),請先轉寫測試,並在第一次執行測試時失敗。 接著,在應用程式中新增可以使測試成功的程式碼。 在本教學課程中,您先撰寫應用程式程式碼,才建立用於驗證的測試,因此測試尚未失敗過。 為了驗證在測試預計會失敗的清況下,測試會確實失敗,請在測試輸入中加入不無效值。
修改
TestDoesNotStartWithUpper
方法中的words
陣列,以包含字串「錯誤」。 您不需要儲存檔案,因為當組建方案以執行測試時,Visual Studio 會自動儲存開啟的檔案。string[] words = { "alphabet", "Error", "zebra", "abc", "αυτοκινητοβιομηχανία", "государство", "1234", ".", ";", " " };
Dim words() As String = { "alphabet", "Error", "zebra", "abc", "αυτοκινητοβιομηχανία", "государство", "1234", ".", ";", " " }
從功能表列中,選取 [測試]>[執行所有測試] 來執行測試。 [測試總管] 視窗表示兩個測試成功,而且有一項失敗。
選取失敗的測試,
TestDoesNotStartWith
。[測試總管] 下方的窗格會顯示判斷提示產生的訊息:「Assert.IsFalse 失敗。 預期為 'Error': false; 實際為: True」。 因為發生失敗,陣列中 [Error] 之後的字串尚未經過測試。
移除您在步驟 1 中加入的字串「Error」後。 重新執行測試,並測試成功通過。
測試程式庫的發行版本
執行程式庫的偵錯組建時,測試都已通過,這次請針對程式庫的發行組建執行一次額外測試。 許多因素 (包括編譯器最佳化) 有時會在偵錯和發行組建之間導致不同的行為。
測試發行組建︰
在 Visual Studio 工具列中,將組建組態從 [偵錯] 變更為 [發行]。
在方案總管 中,以滑鼠右鍵按一下 StringLibrary 專案,然後從內容功能表中選取 [組建] 以重新編譯程式庫。
從功能表列中,選擇 [測試]>[執行所有測試] 來執行單元測試。 所有測試皆通過。
針對測試進行偵錯
若您採用 Visual Studio 作為整合式開發環境,可以遵照教學課程:使用 Visual Studio 對 .NET 主控台應用程式偵錯中列出的流程,為單元測試專案使用的程式碼偵錯。 不要啟動 ShowCase 應用程式專案。以滑鼠右鍵按一下 [StringLibraryTests] 專案,然後在操作功能表中選取 [偵錯測試]。
Visual Studio 會使用附加的偵錯工具啟動測試專案。 執行會在您新增至測試專案或基礎程式庫程式碼的中斷點停止。
其他資源
下一步
在本教學課程中,您已單元測試類別庫。 您可以將程式庫以套件形式發佈至 NuGet,提供給其他人使用。 如需了解執行方式,請遵循 NuGet 教學課程的內容操作:
若將程式庫以 NuGet 套件的形式發佈,其他人即可安裝並使用。 如需了解執行方式,請遵循 NuGet 教學課程的內容操作:
程式庫不需要以套件的形式散發。 其可以與使用套件的主控台應用程式配套。 若需了解如何發佈主控台應用程式,請參閱本系列先前的教學課程: