通过


教程:测试.NET类库

本教程演示如何通过将测试项目添加到解决方案来自动执行单元测试。

先决条件

本教程适用于您在创建 .NET 类库时创建的解决方案。

创建单元测试项目

单元测试在开发和发布期间提供自动化的软件测试。 MSTest 是可供选择的三个测试框架之一。 其他两个是 xUnitnUnit

  1. 启动Visual Studio。

  2. 打开在ClassLibraryProjects中创建的解决方案创建 .NET 类库

  3. 将名为“StringLibraryTest”的新单元测试项目添加到解决方案。

    1. 右键单击 Solution Explorer 中的解决方案,然后选择 Add>New project

    2. 在“添加新项目”页面,在搜索框中输入“mstest”。 从语言列表中选择 C#Visual Basic,然后从平台列表中选择所有平台

    3. 选择“MSTest 测试项目”模板,然后选择“下一步”

    4. 在“配置新项目”页面,在“项目名称”框中输入“StringLibraryTest”。 然后选择“下一步”。

    5. Additional information 页上,在 Framework 框中选择 .NET 10,为 测试运行器 选择 Microsoft.Testing.Platform,然后选择 Create

    输入 MSTest 测试项目的其他信息

  4. Visual Studio创建项目,并使用以下代码在代码窗口中打开类文件。 如果未显示要使用的语言,请更改页面顶部的语言选择器。

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

    单元测试模板创建的源代码负责执行以下操作:

    在运行单元测试时,使用 [TestClass] 标记的测试类中使用 [TestMethod] 标记的每个方法都会自动执行。

  1. 启动Visual Studio Code。

  2. 打开你在创建 .NET 类库中创建的ClassLibraryProjects解决方案。

  3. Solution Explorer 中,选择 New Project,或从命令面板中选择.NET:New Project

  4. 选择 MSTest 测试项目,将其命名为“StringLibraryTest”,选择默认目录,然后选择“ 创建项目”。

    项目模板使用以下代码创建 StringLibraryTest/Test1.cs

    namespace StringLibraryTest;
    
    [TestClass]
    public class Test1
    {
        [TestMethod]
        public void TestMethod1()
        {
        }
    }
    

    单元测试模板创建的源代码负责执行以下操作:

    调用单元测试时,使用 [TestClass] 标记的测试类中使用 [TestMethod] 标记的每个方法都会自动运行。

  1. 打开终端并导航到包含 StringLibrary 和 ShowCase 项目的 tutorials 文件夹。

  2. 创建新的 MSTest 测试项目:

    dotnet new mstest -n StringLibraryTest
    

    项目模板使用以下代码创建 StringLibraryTest/Test1.cs

    namespace StringLibraryTest;
    
    [TestClass]
    public class Test1
    {
        [TestMethod]
        public void TestMethod1()
        {
        }
    }
    

    单元测试模板创建的源代码负责执行以下操作:

    调用单元测试时,使用 [TestClass] 标记的测试类中使用 [TestMethod] 标记的每个方法都会自动运行。

添加项目引用

若要使测试项目能够处理 StringLibrary 该类,请将项目中的 StringLibraryTest 引用添加到 StringLibrary 项目中。

  1. Solution Explorer 中,右键单击StringLibraryTest2 的 Dependencies 节点,然后从上下文菜单中选择 Add Project Reference

  2. “引用管理器 ”对话框中,选择 StringLibrary 旁边的框。

    将 StringLibrary 添加为 StringLibraryTest 的项目引用。

  3. 选择“确定”

  1. Solution Explorer 右键单击“StringLibraryTest”项目,然后选择“Add Project Reference

  2. 选择“StringLibrary”。

  1. 导航到 StringLibraryTest 文件夹并添加项目引用:

    cd StringLibraryTest
    dotnet add reference ../StringLibrary/StringLibrary.csproj
    

添加并运行单元测试方法

当运行单元测试时,标记了TestClassAttribute属性的类中,每个标记了TestMethodAttribute属性的方法都会自动执行。 当找到第一个失败或方法中包含的所有测试都成功时,测试方法将结束。

最常见的测试调用 Assert 类的成员。 许多断言方法至少包含两个参数,其中一个是预期的测试结果,另一个是实际的测试结果。 下表显示了 Assert 类最常调用的一些方法:

断言方法 功能
Assert.AreEqual 验证两个值或对象是否相等。 如果值或对象不相等,则断言失败。
Assert.AreSame 验证两个对象变量引用的是否是同一个对象。 如果这些变量引用不同的对象,则断言失败。
Assert.IsFalse 验证条件是否为 false。 如果条件为 true,则断言失败。
Assert.IsNotNull 验证对象是否不为 null。 如果对象是 null,则断言失败。

还可以在测试方法中使用 Assert.Throws 该方法来指示预期引发的异常类型。 如果未引发指定异常,则测试不通过。

在测试StringLibrary.StartsWithUpper方法时,需要提供以大写字母开头的字符串。 在这种情况下,此方法应返回 true,以便可以调用 Assert.IsTrue 方法。 同样,你希望提供一些以非大写字母开头的字符串。 在这种情况下,此方法应返回 false,以便可以调用 Assert.IsFalse 方法。

由于库方法处理字符串,因此还希望确保它成功处理 空字符串 (String.Emptynull 字符串。 空字符串是没有字符且其为 0 的 Length 字符串。 字符串 null 是尚未初始化的字符串。 可以直接作为静态方法调用 StartsWithUpper 并传递单个 String 参数。 或者,您可以通过将string变量分配给null后,以扩展方法的形式调用StartsWithUpper

你将定义三个方法,每个方法都会调用一个 Assert 方法来处理字符串数组中的每个元素。 你将调用一个方法重载,用于指定在测试失败时显示的错误消息。 消息标识导致失败的字符串。

如何创建测试方法:

  1. Test1.csTest1.vb 代码窗口中,将代码替换为以下代码:

    using UtilityLibraries;
    
    namespace StringLibraryTest
    {
        [TestClass]
        public sealed class Test1
        {
            [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, $"Expected for '{word}': true; Actual: {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, $"Expected for '{word}': false; Actual: {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, $"Expected for '{word ?? "<null>"}': false; Actual: {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 方法中大写字符的测试包括希腊大写字母 Α(U+0391)和西里尔大写字母 М(U+041C)。 TestDoesNotStartWithUpper 方法中的小写字符的测试包括希腊文小写字母 alpha (U+03B1) 和西里尔文小写字母 Ghe (U+0433)。

  2. 在菜单栏上,选择“文件>另存为”Test1.cs“或”文件>另存为Test1.vb为”。 在“文件另存为”对话框中,选择“保存”按钮旁边的箭头,然后选择“保存时使用编码”

  3. 确认另存为 对话框中,选择「是」按钮以保存文件。

  4. 在“高级保存选项”对话框的“编码”下拉列表中,选择“Unicode (UTF-8 带签名) - 代码页 65001”,然后选择“确定”

    如果无法将源代码另存为 UTF8 编码的文件,Visual Studio可能会将其另存为 ASCII 文件。 发生这种情况时,运行时不会准确解码 ASCII 范围之外的 UTF8 字符,并且测试结果不正确。

  5. 在菜单栏上,选择测试>运行所有测试。 如果 测试资源管理器 窗口没有打开,可以通过选择 测试>测试资源管理器来打开它。 “通过的测试”部分列出了三个测试,“摘要”部分报告了测试运行结果。

    测试资源管理器窗口 通过测试的“测试资源管理器”窗口

  1. 打开 StringLibraryTest/Test1.cs, 并将所有代码替换为以下代码。

    using UtilityLibraries;
    
    namespace StringLibraryTest
    {
        [TestClass]
        public sealed class Test1
        {
            [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, $"Expected for '{word}': true; Actual: {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, $"Expected for '{word}': false; Actual: {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, $"Expected for '{word ?? "<null>"}': false; Actual: {result}");
                }
            }
        }
    }
    

    TestStartsWithUpper 方法中大写字符的测试包括希腊大写字母 Α(U+0391)和西里尔大写字母 М(U+041C)。 TestDoesNotStartWithUpper 方法中的小写字符的测试包括希腊文小写字母 alpha (U+03B1) 和西里尔文小写字母 Ghe (U+0433)。

  2. 保存所做的更改。

生成并运行测试

  1. Solution Explorer 中,右键单击解决方案并选择“生成”Build,或从命令面板中选择 .NET:Build

  2. 选择 “测试 ”窗口,选择“ 运行测试 ”或“命令面板”,选择“ 测试:运行所有测试”。

    Visual Studio Code测试资源管理器

  1. 打开 StringLibraryTest/Test1.cs, 并将所有代码替换为以下代码:

    using UtilityLibraries;
    
    namespace StringLibraryTest
    {
        [TestClass]
        public sealed class Test1
        {
            [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, $"Expected for '{word}': true; Actual: {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, $"Expected for '{word}': false; Actual: {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, $"Expected for '{word ?? "<null>"}': false; Actual: {result}");
                }
            }
        }
    }
    

    TestStartsWithUpper 方法中大写字符的测试包括希腊大写字母 Α(U+0391)和西里尔大写字母 М(U+041C)。 TestDoesNotStartWithUpper 方法中的小写字符的测试包括希腊文小写字母 alpha (U+03B1) 和西里尔文小写字母 Ghe (U+0433)。

  2. 保存更改并运行测试:

    dotnet test
    

    测试应通过。

处理测试失败

如果您进行测试驱动开发(TDD),需要先编写测试,并且第一次运行时测试会失败。 接着将可以使测试成功的代码添加到应用。 在本教程中,先编写了测试要验证的应用代码然后才创建测试,所以没有看到测试失败。 若要验证测试是否在预期失败时失败,请在测试输入中添加无效值。

  1. 通过修改 words 方法中的 TestDoesNotStartWithUpper 数组来包含字符串“Error”。

    string[] words = { "alphabet", "Error", "zebra", "abc", "αυτοκινητοβιομηχανία", "государство",
                       "1234", ".", ";", " " };
    
    Dim words() As String = { "alphabet", "Error", "zebra", "abc", "αυτοκινητοβιομηχανία", "государство",
                       "1234", ".", ";", " " }
    
    
  1. 从菜单栏中选择测试>运行所有测试,运行测试。 “测试资源管理器”窗口指示有两个测试成功,还有一个失败。

    未通过测试的“测试资源管理器”窗口

  2. 选择失败的测试,TestDoesNotStartWith

    “测试资源管理器”窗口显示断言生成的消息:“Assert.IsFalse 失败。 预期“Error”:false;实际:true”。由于此错误,未测试“Error”之后的数组中的字符串。

    显示 IsFalse 断言失败的“测试资源管理器”窗口

  1. 通过在编辑器中单击测试旁边的绿色错误来运行测试。

    输出显示测试失败,并提供失败测试的错误消息:“Assert.IsFalse 失败。 期望值为“Error”:false;实际值为:True。 由于失败,数组中“错误”之后的字符串未进行测试。

    Visual Studio Code测试失败

  1. 运行测试:

    dotnet test
    

    输出显示测试失败,并提供失败测试的错误消息:“Assert.IsFalse 失败。 期望值为“Error”:false;实际值为:True。 由于失败,数组中“错误”之后的字符串未进行测试。

  1. 删除添加的字符串“Error”。

  2. 重新运行测试,测试将通过。

测试库的发行版本

在运行库的调试版本时,测试已全部通过,接下来针对库的发布版本运行测试。 许多因素(包括编译器优化)有时会在调试和发布生成之间产生不同的行为。

若要测试发行版本,请执行以下操作:

  1. 在Visual Studio工具栏中,将生成配置从 Debug 更改为 Release

  2. Solution Explorer 中,右键单击 StringLibrary 项目,然后从上下文菜单中选择 Build 以重新编译库。

  3. 从菜单栏中选择测试>运行所有测试,运行单元测试。 测试通过。

请使用 Release 构建配置运行测试:

dotnet test StringLibraryTest/StringLibraryTest.csproj --configuration Release

测试通过。

请使用 Release 构建配置运行测试:

dotnet test --configuration Release

测试通过。

调试测试

如果使用 Visual Studio 作为 IDE,则可以使用 Tutorial 中所示的相同过程:调试.NET控制台应用程序使用单元测试项目调试代码。 不要启动 ShowCase 应用项目,而是右键单击 StringLibraryTests 项目,并从上下文菜单中选择 调试测试

Visual Studio使用附加的调试器启动测试项目。 执行会在添加到测试项目或基础库代码的任何断点处停止。

如果使用 Visual Studio Code 作为 IDE,则可以使用 Debug .NET 控制台应用程序中显示的相同过程使用单元测试项目调试代码。 打开 StringLibraryTest/Test1.cs,并选择第 7 行到 8 行之间的当前文件中的“调试测试”,而不是启动 ShowCase 应用项目。 如果找不到它,请按 Ctrl+Shift+P 打开命令面板并输入 重载窗口

Visual Studio Code使用附加的调试器启动测试项目。 添加到测试项目或底层库代码中的任何断点处,执行将停止。

其他资源

清理资源

GitHub在处于非活动状态 30 天后自动删除 Codespace。 如果打算探索本系列中的更多教程,您可以保留 Codespace 配置。 如果已准备好访问 .NET 站点下载 .NET SDK,则可以删除 Codespace。 若要删除 Codespace,请打开浏览器窗口并导航到 Codespaces。 可在窗口中看到代码空间的列表。 在“学习教程代码空间”的条目中选择三个点(...)。 然后选择“删除”。

后续步骤

在本教程中,你对类库进行了单元测试。 可以通过将其作为包发布到 NuGet ,使库可供其他人使用。 若要了解如何操作,请遵循 NuGet 教程:

如果将库作为 NuGet 包发布,其他人可以安装并使用它。 若要了解如何操作,请遵循 NuGet 教程:

库并非必须作为包进行分发。 它还可与使用它的控制台应用捆绑在一起。 若要了解如何发布控制台应用,请参阅本系列中前面的教程: