在 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#
在“解决方案资源管理器” 中,打开“ClassLibraryProjects” 解决方案节点的上下文菜单,再依次选择“添加” > “新项目” 。
在“添加新项目” 对话框中,选择“Visual C#” 节点。然后,依次选择“.NET Core” 节点和“MSTest 测试项目(.NET Core)” 项目模板。在“名称” 文本框中,输入项目名称“StringLibraryTest”。选择“确定” ,创建单元测试项目。
备注
除了 MSTest 测试项目之外,还可以使用 Visual Studio 为 .NET Core 创建 xUnit 测试项目。
- 此时,Visual Studio 会创建项目,并在代码窗口中打开 UnitTest1.cs 文件。
单元测试模板创建的源代码负责执行以下操作:
它会导入 Microsoft.VisualStudio.TestTools.UnitTesting 命名空间,其中包含用于单元测试的类型。
向
UnitTest1
类应用 TestClassAttribute 特性。测试类中标记有 [TestMethod] 属性的所有测试方法都会在单元测试运行时自动执行。它会应用 TestMethodAttribute 属性,将
TestMethod1
定义为在单元测试运行时自动执行的测试方法。
- 在“解决方案资源管理器” 中,右键单击“StringLibraryTest” 项目的“依赖项” 节点,并从上下文菜单中选择“添加引用” 。
- 在“引用管理器” 对话框中,展开“项目” 节点,并选中“StringLibrary” 旁边的框。添加对
StringLibrary
程序集的引用后,编译器可以查找 StringLibrary 方法。选择“确定” 按钮。这会添加对类库项目StringLibrary
的引用。
在“解决方案资源管理器” 中,打开“ClassLibraryProjects” 解决方案节点的上下文菜单,再依次选择“添加” > “新项目” 。
在“添加新项目” 对话框中,选择“Visual Basic” 节点。然后,依次选择“.NET Core” 节点和“MSTest 测试项目(.NET Core)” 项目模板。在“名称” 文本框中,输入项目名称“StringLibraryTest”。选择“确定” ,创建单元测试项目。
备注
除了 MSTest 测试项目之外,还可以使用 Visual Studio 为 .NET Core 创建 xUnit 测试项目。
- 此时,Visual Studio 会创建项目,并在代码窗口中打开 UnitTest1.vb 文件。
单元测试模板创建的源代码负责执行以下操作:
它会导入 Microsoft.VisualStudio.TestTools.UnitTesting 命名空间,其中包含用于单元测试的类型。
向
UnitTest1
类应用 TestClassAttribute 特性。测试类中标记有 TestMethodAttribute 特性的所有测试方法都会在单元测试运行时自动执行。它会应用 TestMethodAttribute 属性,将
TestMethod1
定义为在单元测试运行时自动执行的测试方法。
- 在“解决方案资源管理器” 中,右键单击“StringLibraryTest” 项目的“依赖项” 节点,并从上下文菜单中选择“添加引用” 。
- 在“引用管理器” 对话框中,展开“项目” 节点,并选中“StringLibrary” 旁边的框。添加对
StringLibrary
程序集的引用后,编译器可以查找 StringLibrary 方法。选择“确定” 按钮。这会添加对类库项目StringLibrary
的引用。
添加并运行单元测试方法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#
将 UnitTest1.cs 代码窗口中的代码替换为以下代码:
using System;
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
方法中测试的大写字符包括希腊文大写字母 alpha (U+0391) 和西里尔文大写字母 EM (U+041C),TestDoesNotStartWithUpper
方法中测试的小写字符包括希腊文小写字母 alpha (U+03B1) 和西里尔文小写字母 Ghe (U+0433)。
- 在菜单栏上,选择“文件” > “将 UnitTest1.cs 另存为” 。在“文件另存为” 对话框中,选择“保存” 按钮旁边的箭头,然后选择“保存时使用编码” 。
- 将 UnitTest1.vb 代码窗口中的代码替换为以下代码:
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.vb 另存为” 。在“文件另存为” 对话框中,选择“保存” 按钮旁边的箭头,然后选择“保存时使用编码” 。
在“确认另存为” 对话框中,选择“是” 按钮,保存文件。
在“高级保存选项” 对话框的“编码” 下拉列表中,选择“Unicode (UTF-8 带签名) - 代码页 65001” ,然后选择“确定” 。
如果无法将源代码保存为 UTF8 编码文件,Visual Studio 可能会将其另存为 ASCII 文件。在这种情况下,运行时将无法准确解码 ASCII 范围以外的 UTF8 字符,且测试结果也会不准确。
- 在菜单栏上,选择“测试” > “运行” > “所有测试” 。此时,“测试资源管理器” 窗口打开并显示测试已成功运行。“通过的测试” 部分列出了三个测试,“摘要” 部分报告了测试运行结果。
处理未通过的测试Handling test failures
由于运行的测试均通过,因此需进行少量改动,以使其中一个测试方法失败:
- 通过修改
TestDoesNotStartWithUpper
方法中的words
数组来包含字符串“Error”。由于 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”。重新运行测试,测试将通过。
测试库的发行版本Testing the Release version of the library
现已测试库的调试版本。至此,测试已全部通过,且已充分测试库,应对库的发布版本再运行一次这些测试。许多因素(包括编译器优化)有时可能会导致调试版本和发行版本出现行为差异。
若要测试发行版本,请执行以下操作:
- 在 Visual Studio 工具栏中,将生成配置从 “调试” 更改为 “发行” 。
- 在“解决方案资源管理器” 中,右键单击“StringLibrary” 项目,从上下文菜单中选择“生成” ,重新编译库。
- 从菜单栏中选择“测试” > “运行” > “所有测试” ,运行单元测试。测试通过。
至此,已完成对库的测试,下一步就是使其可供调用方使用。可以将类库与一个或多个应用程序捆绑在一起,也可以 NuGet 包的形式分发类库。有关详细信息,请参阅使用 .NET Standard 类库。