在 Visual Studio 2017 中使用 .NET Core 测试 .NET Standard 库Test a .NET Standard library with .NET Core in Visual Studio 2017

本文内容

在 Visual Studio 2017 中使用 C# 和 .NET Core 生成 .NET Standard 库在 Visual Studio 2017 中使用 Visual Basic 和 .NET Core 生成 .NET Standard 库中,创建了简单的类库,用于向 String 类添加扩展方法。现在,将创建一个单元测试,用于确保此类库能够按预期运行。向在上一篇文章中创建的解决方案添加单元测试项目。

创建单元测试项目Creating a unit test project

若要创建单元测试项目,请执行以下操作:

  • C#
  • Visual Basic

  • 在“解决方案资源管理器” 中,打开“ClassLibraryProjects” 解决方案节点的上下文菜单,再依次选择“添加” > “新项目” 。

  • 在“添加新项目” 对话框中,选择“Visual C#” 节点。然后,依次选择“.NET Core” 节点和“MSTest 测试项目(.NET Core)” 项目模板。在“名称” 文本框中,输入项目名称“StringLibraryTest”。选择“确定” ,创建单元测试项目。

“添加新项目”对话框,其中显示单元测试项目 - C#

备注

除了 MSTest 测试项目之外,还可以使用 Visual Studio 为 .NET Core 创建 xUnit 测试项目。

  • 此时,Visual Studio 会创建项目,并在代码窗口中打开 UnitTest1.cs 文件。

Visual Studio 代码窗口,用于单元测试项目的类和方法 - C#

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

  • 在“解决方案资源管理器” 中,右键单击“StringLibraryTest” 项目的“依赖项” 节点,并从上下文菜单中选择“添加引用” 。

StringLibraryTest 依赖项的上下文菜单 - C#

  • 在“引用管理器” 对话框中,展开“项目” 节点,并选中“StringLibrary” 旁边的框。添加对 StringLibrary 程序集的引用后,编译器可以查找 StringLibrary 方法。选择“确定” 按钮。这会添加对类库项目 StringLibrary 的引用。

Visual Studio“添加项目引用”对话框

  • 在“解决方案资源管理器” 中,打开“ClassLibraryProjects” 解决方案节点的上下文菜单,再依次选择“添加” > “新项目” 。

  • 在“添加新项目” 对话框中,选择“Visual Basic” 节点。然后,依次选择“.NET Core” 节点和“MSTest 测试项目(.NET Core)” 项目模板。在“名称” 文本框中,输入项目名称“StringLibraryTest”。选择“确定” ,创建单元测试项目。

“添加新项目”对话框,其中显示单元测试项目 - Visual Basic

备注

除了 MSTest 测试项目之外,还可以使用 Visual Studio 为 .NET Core 创建 xUnit 测试项目。

  • 此时,Visual Studio 会创建项目,并在代码窗口中打开 UnitTest1.vb 文件。

Visual Studio 代码窗口,用于单元测试项目的类和方法 - Visual Basic

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

  • 在“解决方案资源管理器” 中,右键单击“StringLibraryTest” 项目的“依赖项” 节点,并从上下文菜单中选择“添加引用” 。

StringLibraryTest 依赖项的上下文菜单

  • 在“引用管理器” 对话框中,展开“项目” 节点,并选中“StringLibrary” 旁边的框。添加对 StringLibrary 程序集的引用后,编译器可以查找 StringLibrary 方法。选择“确定” 按钮。这会添加对类库项目 StringLibrary 的引用。

Visual Studio“添加项目引用”对话框 - Visual Basic

添加并运行单元测试方法Adding and running unit test methods

运行单元测试时,Visual Studio 执行单元测试类(对其应用了 TestClassAttribute 特性的类)中标记有 TestMethodAttribute 特性的所有方法。当第一次遇到测试不通过或测试方法中的所有测试均已成功通过时,测试方法终止。

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

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

还可向测试方法应用 ExpectedExceptionAttribute 特性。它指明了测试方法预计会引发的异常类型。如果未抛出指定异常,则测试不通过。

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

由于库方法处理的是字符串,因此还需要确保它能够成功处理空字符串 (String.Empty)(不含字符且 Length 为 0 的有效字符串)和 null 字符串(尚未初始化的字符串)。如果对 String 实例调用 StartsWithUpper 作为扩展方法,无法向其传递 null 字符串。不过,还可以直接将其作为静态方法进行调用,并向其传递一个 String 自变量。

将定义三个方法,每个方法都会对字符串数组中的各个元素反复调用它的 Assert 方法。由于测试方法在第一次遇到测试不通过时会立即失败,因此将调用方法重载,以便传递字符串来指明方法调用中使用的字符串值。

创建测试方法:

  • C#
  • Visual Basic

  • 将 UnitTest1.cs 代码窗口中的代码替换为以下代码:

  1. using System;
  2. using Microsoft.VisualStudio.TestTools.UnitTesting;
  3. using UtilityLibraries;
  4. namespace StringLibraryTest
  5. {
  6. [TestClass]
  7. public class UnitTest1
  8. {
  9. [TestMethod]
  10. public void TestStartsWithUpper()
  11. {
  12. // Tests that we expect to return true.
  13. string[] words = { "Alphabet", "Zebra", "ABC", "Αθήνα", "Москва" };
  14. foreach (var word in words)
  15. {
  16. bool result = word.StartsWithUpper();
  17. Assert.IsTrue(result,
  18. String.Format("Expected for '{0}': true; Actual: {1}",
  19. word, result));
  20. }
  21. }
  22. [TestMethod]
  23. public void TestDoesNotStartWithUpper()
  24. {
  25. // Tests that we expect to return false.
  26. string[] words = { "alphabet", "zebra", "abc", "αυτοκινητοβιομηχανία", "государство",
  27. "1234", ".", ";", " " };
  28. foreach (var word in words)
  29. {
  30. bool result = word.StartsWithUpper();
  31. Assert.IsFalse(result,
  32. String.Format("Expected for '{0}': false; Actual: {1}",
  33. word, result));
  34. }
  35. }
  36. [TestMethod]
  37. public void DirectCallWithNullOrEmpty()
  38. {
  39. // Tests that we expect to return false.
  40. string[] words = { string.Empty, null };
  41. foreach (var word in words)
  42. {
  43. bool result = StringLibrary.StartsWithUpper(word);
  44. Assert.IsFalse(result,
  45. String.Format("Expected for '{0}': false; Actual: {1}",
  46. word == null ? "<null>" : word, result));
  47. }
  48. }
  49. }
  50. }

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

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

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

  • 将 UnitTest1.vb 代码窗口中的代码替换为以下代码:
  1. Imports Microsoft.VisualStudio.TestTools.UnitTesting
  2. Imports UtilityLibraries
  3. Namespace StringLibraryTest
  4. <TestClass>
  5. Public Class UnitTest1
  6. <TestMethod>
  7. Public Sub TestStartsWithUpper()
  8. ' Tests that we expect to return true.
  9. Dim words() As String = {"Alphabet", "Zebra", "ABC", "Αθήνα", "Москва"}
  10. For Each word In words
  11. Dim result As Boolean = word.StartsWithUpper()
  12. Assert.IsTrue(result,
  13. $"Expected for '{word}': true; Actual: {result}")
  14. Next
  15. End Sub
  16. <TestMethod>
  17. Public Sub TestDoesNotStartWithUpper()
  18. ' Tests that we expect to return false.
  19. Dim words() As String = {"alphabet", "zebra", "abc", "αυτοκινητοβιομηχανία", "государство",
  20. "1234", ".", ";", " "}
  21. For Each word In words
  22. Dim result As Boolean = word.StartsWithUpper()
  23. Assert.IsFalse(result,
  24. $"Expected for '{word}': false; Actual: {result}")
  25. Next
  26. End Sub
  27. <TestMethod>
  28. Public Sub DirectCallWithNullOrEmpty()
  29. ' Tests that we expect to return false.
  30. Dim words() As String = {String.Empty, Nothing}
  31. For Each word In words
  32. Dim result As Boolean = StringLibrary.StartsWithUpper(word)
  33. Assert.IsFalse(result,
  34. $"Expected for '{If(word Is Nothing, "<null>", word)}': false; Actual: {result}")
  35. Next
  36. End Sub
  37. End Class
  38. End Namespace

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

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

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

  • 在“确认另存为” 对话框中,选择“是” 按钮,保存文件。

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

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

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

  • 在菜单栏上,选择“测试” > “运行” > “所有测试” 。此时,“测试资源管理器” 窗口打开并显示测试已成功运行。“通过的测试” 部分列出了三个测试,“摘要” 部分报告了测试运行结果。

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

处理未通过的测试Handling test failures

由于运行的测试均通过,因此需进行少量改动,以使其中一个测试方法失败:

  • 通过修改 TestDoesNotStartWithUpper 方法中的 words 数组来包含字符串“Error”。由于 Visual Studio 将在生成运行测试的解决方案时自动保存打开的文件,因此无需手动保存。
  1. string[] words = { "alphabet", "Error", "zebra", "abc", "αυτοκινητοβιομηχανία", "государство",
  2. "1234", ".", ";", " " };
  1. Dim words() As String = { "alphabet", "Error", "zebra", "abc", "αυτοκινητοβιομηχανία", "государство",
  2. "1234", ".", ";", " " }
  • 从菜单栏中选择“测试” > “运行” > “所有测试” ,运行测试。“测试资源管理器” 窗口指示有两个测试成功,还有一个失败。

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

  • 在“未通过的测试” 部分中,选择未通过的测试 TestDoesNotStartWith。“测试资源管理器”窗口显示断言生成的消息: “Assert.IsFalse 失败。“Error”应返回 false;实际返回 True”。由于此次失败,数组中“Error”之后的所有字符串都未进行测试。

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

  • 撤消在步骤 1 中执行的修改并删除字符串“Error”。重新运行测试,测试将通过。

测试库的发行版本Testing the Release version of the library

现已测试库的调试版本。至此,测试已全部通过,且已充分测试库,应对库的发布版本再运行一次这些测试。许多因素(包括编译器优化)有时可能会导致调试版本和发行版本出现行为差异。

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

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

Visual Studio 工具栏,其中突出显示发布版本

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

带有生成命令的 StringLibrary 上下文菜单

  • 从菜单栏中选择“测试” > “运行” > “所有测试” ,运行单元测试。测试通过。

至此,已完成对库的测试,下一步就是使其可供调用方使用。可以将类库与一个或多个应用程序捆绑在一起,也可以 NuGet 包的形式分发类库。有关详细信息,请参阅使用 .NET Standard 类库