教程:使用 Visual Studio 和 .NET 测试 .NET 类库

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

先决条件

本教程适用于在使用 Visual Studio 创建 .NET 类库中创建的解决方案。

创建单元测试项目

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

  1. 启动 Visual Studio。

  2. 打开在ClassLibraryProjects中创建的 解决方案。

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

    1. “解决方案资源管理器”中,右键单击解决方案并选择“添加”>“新建项目”

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

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

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

    5. 附加信息页的框架框中选择 .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 运行单元测试时,它会执行一个用 TestClassAttribute 属性标记的类中每个用 TestMethodAttribute 属性标记的方法。 当找到第一个失败或方法中包含的所有测试都成功时,测试方法将结束。

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

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

您还可以在测试方法中使用 Assert.ThrowsException 方法(如果使用 MSTest 3.8 及更高版本,还可以使用 Assert.ThrowsAssert.ThrowsExactly)来指示预期会引发的异常类型。 如果未引发指定异常,则测试不通过。

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

由于库方法处理的是字符串,因此还需要确保它能够成功处理空字符串 (String.Empty)(不含字符且 Length 为 0 的有效字符串)和 null 字符串(尚未初始化的字符串)。 直接作为静态方法调用 StartsWithUpper 并传递单个 String 参数。 或者调用分配给nullstring变量上的StartsWithUpper扩展方法。

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

如何创建测试方法:

  1. 将 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, $"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. 在菜单栏上,选择 文件>“将UnitTest1.cs另存为” 或 文件>“将UnitTest1.vb另存为”。 在“文件另存为”对话框中,选择“保存”按钮旁边的箭头,然后选择“保存时使用编码”

    Visual Studio“文件另存为”对话框

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

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

    Visual Studio“高级保存选项”对话框

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

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

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

处理测试失败

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

  1. 通过修改 words 方法中的 TestDoesNotStartWithUpper 数组来包含字符串“Error”。 由于 Visual Studio 将在生成运行测试的解决方案时自动保存打开的文件,因此无需手动保存。

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

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

  3. 选择失败的测试,TestDoesNotStartWith

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

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

  4. 删除在步骤 1 中添加的字符串“Error”。 重新运行测试,测试将通过。

测试库的发行版本

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

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

  1. 在 Visual Studio 工具栏中,将生成配置从 “调试” 更改为 “发行”

    突出显示发布版本的 Visual Studio 工具栏

  2. 在“解决方案资源管理器”中,右键单击“StringLibrary”项目,从上下文菜单中选择“生成”,重新编译库。

    在 StringLibrary 上下文菜单中使用生成命令

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

调试测试

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

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

其他资源

后续步骤

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

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

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