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

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

先决条件

创建单元测试项目

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

  1. 启动 Visual Studio Code。

  2. 打开在使用 Visual Studio Code 创建 .NET 类库中创建的 ClassLibraryProjects 解决方案。

  3. 解决方案资源管理器中,选择 新建项目,或者从命令面板中选择 .NET:新建项目

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

    项目模板使用以下代码创建 UnitTest1.cs 文件:

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

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

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

添加项目引用

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

  1. 解决方案资源管理器上右键单击“StringLibraryTest”项目,然后选择添加项目引用

  2. 选择“StringLibrary”。

添加和运行单元测试方法

调用单元测试时,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.Emptynull 字符串。 空字符串是没有字符且其 Length 为 0 的字符串。 null 字符串是尚未初始化的字符串。 可以直接将 StartsWithUpper 作为静态方法调用,并传递单个 String 参数。 也可以对分配给 nullstring 变量调用 StartsWithUpper 作为扩展方法。

你将定义三个方法,其中每个方法针对字符串数组中的每个元素调用 Assert 方法。 你将调用方法重载,以便指定在测试失败时要显示的错误消息。 该消息标识导致失败的字符串。

创建测试方法:

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

    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));
                }
            }
        }
    }
    

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

  2. 保存更改。

生成并运行测试

  1. 解决方案资源管理器中,右键单击该解决方案并选择 生成,或者从命令面板中选择 .NET:生成

  2. 选择 测试 窗口,选择 运行测试,或从命令面板中选择 测试:运行所有测试

    Visual Studio Code 测试资源管理器

处理测试失败

如果要进行测试驱动开发(TDD),先编写测试,它们在首次运行时会失败。 然后将代码添加到使测试成功的应用。 在本教程中,你在编写它验证的应用代码后创建了测试,因此你没有看到测试失败。 若要验证测试是否在预期失败时失败,请在测试输入中添加无效值。

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

    string[] words = { "alphabet", "Error", "zebra", "abc", "αυτοκινητοβιομηχανία", "государство",
                       "1234", ".", ";", " " };
    
  2. 通过在编辑器中单击测试旁边的绿色错误来运行测试。

    输出显示测试失败,并提供失败测试的错误消息:“Assert.IsFalse 失败。 “Error”应返回 false;实际返回 True”。 由于失败,“错误”后数组中的字符串没有被测试。

    Visual Studio Code 测试失败

  3. 删除在步骤中添加的字符串“Error”。

  4. 重新运行测试并测试通过。

测试库的发行版本

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

  1. 使用发布版本配置运行测试:

    dotnet test StringLibraryTest/StringLibraryTest.csproj --configuration Release
    

    测试通过。

调试测试

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

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

其他资源

后续步骤

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

使用 dotnet CLI 创建和发布包

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

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

使用 Visual Studio Code 发布 .NET 控制台应用程序

Visual Studio Code 扩展 C# 开发工具包提供了更多用于开发 C# 应用和库的工具: