创建数据驱动单元测试
可以使用托管代码的 Microsoft 单元测试框架 (MSTest) 设置单元测试方法以从数据源检索值。 该方法针对数据源中的每个行连续运行,这样就可以使用单个方法轻松测试各种输入。
数据驱动单元测试可以使用以下任一类型:
- 使用
DataRow
属性的内联数据 - 使用
DynamicData
属性的成员数据 - 来自一些使用
DataSource
属性的已知源提供程序
正在测试的方法
例如,假设你有:
名为
MyBank
的解决方案,可接受和处理不同类型的帐户的事务。MyBank
中名为BankDb
的项目,用于管理帐户的事务。BankDb
项目中名为Maths
的类,用于执行数学函数以确保任何事务对银行有利。名为
BankDbTests
的单元测试项目,用于测试BankDb
组件的行为。名为
MathsTests
的单元测试类,用于验证Maths
类的行为。
我们将在 Maths
中测试使用循环添加两个整数的方法:
public int AddIntegers(int first, int second)
{
int sum = first;
for (int i = 0; i < second; i++)
{
sum += 1;
}
return sum;
}
对测试方法进行测试
内联数据驱动的测试
对于内联测试,MSTest 使用 DataRow
来指定数据驱动测试使用的值。 此示例中的测试针对每个数据行连续运行。
[TestMethod]
[DataRow(1, 1, 2)]
[DataRow(2, 2, 4)]
[DataRow(3, 3, 6)]
[DataRow(0, 0, 1)] // The test run with this row fails
public void AddIntegers_FromDataRowTest(int x, int y, int expected)
{
var target = new Maths();
int actual = target.AddIntegers(x, y);
Assert.AreEqual(expected, actual,
"x:<{0}> y:<{1}>",
new object[] {x, y});
}
成员数据驱动的测试
MSTest 使用 DynamicData
属性来指定成员的名称、类型和定义类型(默认使用当前类型),该成员将提供数据驱动测试使用的数据。
注释
在 MSTest 3.8 之前,DynamicDataSourceType
枚举具有两个成员,Property
和 Method
。 默认值为 Property
。 从 MSTest 3.8 开始,新成员 AutoDetect
添加到枚举中,这是默认值。 因此,不再需要指定 DynamicDataSourceType
。
public static IEnumerable<object[]> AdditionData
{
get
{
return new[]
{
new object[] { 1, 1, 2 },
new object[] { 2, 2, 4 },
new object[] { 3, 3, 6 },
new object[] { 0, 0, 1 }, // The test run with this row fails
};
}
}
[TestMethod]
[DynamicData(nameof(AdditionData))]
public void AddIntegers_FromDynamicDataTest(int x, int y, int expected)
{
var target = new Maths();
int actual = target.AddIntegers(x, y);
Assert.AreEqual(expected, actual,
"x:<{0}> y:<{1}>",
new object[] {x, y});
}
还可以使用 DynamicData
属性的 DynamicDataDisplayName
属性替代默认生成的显示名称。 显示名称方法签名必须 public static string
并接受两个参数,第一个类型 MethodInfo
和第二个类型 object[]
。
public static string GetCustomDynamicDataDisplayName(MethodInfo methodInfo, object[] data)
{
return string.Format("DynamicDataTestMethod {0} with {1} parameters", methodInfo.Name, data.Length);
}
[DynamicData(nameof(AdditionData), DynamicDataDisplayName = nameof(GetCustomDynamicDataDisplayName))]
源提供程序数据驱动的测试
创建数据源驱动的单元测试涉及以下步骤:
创建一个数据源,其中包含在测试方法中使用的值。 数据源可以是任何在运行测试的计算机上注册的类型。
将 TestContext 类型的公共
TestContext
属性添加到测试类。创建单元测试方法
向其添加 DataSourceAttribute 属性。
使用 DataRow 索引器属性检索在测试中使用的值。
创建数据源
若要测试 AddIntegers
方法,请创建一个数据源,该数据源指定参数的一系列值以及预期返回的总和。 在本示例中,我们将创建名为 MathsData
的 Sql Compact 数据库和包含以下列名和值的名为 AddIntegersData
的表
FirstNumber | SecondNumber | Sum |
---|---|---|
0 | 1 | 1 |
1 | 1 | 2 |
2 | -3 | -1 |
将 TestContext 添加到测试类
单元测试框架创建一个 TestContext
对象,用于存储数据驱动测试的数据源信息。 然后,框架将此对象设置为所创建的 TestContext
属性的值。
public TestContext TestContext { get; set; }
在您的测试方法中,您可以通过 TestContext
的 DataRow
索引器属性访问数据。
注释
.NET Core 不支持 DataSource 属性。 如果尝试在.NET Core、UWP 或 WinUI 单元测试项目中以这种方式访问测试数据,您可能会看到与 "‘TestContext’ 不包含 'DataRow' 的定义,且找不到接受 'TestContext' 类型的第一个参数的可访问扩展方法 'DataRow'" 类似的错误(是否缺少 using 指令或程序集引用?)。
编写测试方法
AddIntegers
的测试方法非常简单。 对于数据源中的每个行,请使用 FirstNumber 和 SecondNumber 列值作为参数调用 AddIntegers
,并针对 Sum 列值验证返回值:
[TestMethod]
[DataSource(@"Provider=Microsoft.SqlServerCe.Client.4.0; Data Source=C:\Data\MathsData.sdf;", "Numbers")]
public void AddIntegers_FromDataSourceTest()
{
var target = new Maths();
// Access the data
int x = Convert.ToInt32(TestContext.DataRow["FirstNumber"]);
int y = Convert.ToInt32(TestContext.DataRow["SecondNumber"]);
int expected = Convert.ToInt32(TestContext.DataRow["Sum"]);
int actual = target.AddIntegers(x, y);
Assert.AreEqual(expected, actual,
"x:<{0}> y:<{1}>",
new object[] {x, y});
}
指定 DataSourceAttribute
DataSource
属性指定数据源的连接字符串以及测试方法中使用的表的名称。 连接字符串中的确切信息会有所不同,具体取决于所使用的数据源类型。 在此示例中,我们使用了 SqlServerCe 数据库。
[DataSource(@"Provider=Microsoft.SqlServerCe.Client.4.0;Data Source=C:\Data\MathsData.sdf", "AddIntegersData")]
注意
连接字符串可以包含敏感数据(例如密码)。 连接字符串以纯文本形式存储在源代码和已编译的程序集中。 限制对源代码和程序集的访问以保护此敏感信息。
DataSource 属性有三个构造函数。
[DataSource(dataSourceSettingName)]
具有一个参数的构造函数使用存储在解决方案 app.config 文件中的连接信息。 dataSourceSettingsName 是配置文件中指定连接信息的 Xml 元素的名称。
使用 app.config 文件可以更改数据源的位置,而无需更改单元测试本身。 有关如何创建和使用 app.config 文件的信息,请参阅 演练:使用配置文件定义数据源
[DataSource(connectionString, tableName)]
具有两个参数的 DataSource
构造函数指定数据源的连接字符串和包含测试方法数据的表的名称。
连接字符串取决于数据源类型的类型,但它应包含一个提供程序元素,该元素指定数据提供程序的固定名称。
[DataSource(
dataProvider,
connectionString,
tableName,
dataAccessMethod
)]
使用 TestContext.DataRow 访问数据
若要访问 AddIntegersData
表中的数据,请使用 TestContext.DataRow
索引器。 DataRow
是一个 DataRow 对象,因此按索引或列名检索列值。 由于这些值作为对象返回,因此将它们转换为适当的类型:
int x = Convert.ToInt32(TestContext.DataRow["FirstNumber"]);
运行测试和查看结果
编写完测试方法后,生成测试项目。 测试方法显示在 测试资源管理器 的 未运行测试 组中。 运行、写入和重新运行测试时,测试资源管理器 在 失败的测试组、通过的测试以及 未运行测试显示结果。 可以选择 运行所有 来运行所有测试,或选择 运行 选择要运行的测试子集。
当运行测试时,位于“测试资源管理器”顶部的测试结果栏是动态显示的。 在测试运行结束时,如果所有测试都已通过,则条形图将为绿色,如果任何测试均失败,则为红色。 测试运行摘要显示在 测试资源管理器 窗口底部的详细信息窗格中。 选择一个测试,在底部窗格中查看该测试的详细信息。
注释
每行数据都有一个结果,还有一个摘要结果。 如果每行数据测试均通过,则摘要运行显示为“已通过”。 如果测试在任何数据行上失败,摘要运行将显示为 失败。
如果在示例中运行了任何 AddIntegers_FromDataRowTest
、AddIntegers_FromDynamicDataTest
或 AddIntegers_FromDataSourceTest
方法,结果栏将变为红色,并且测试方法将移动到 失败的测试区。 如果数据源中的任何迭代方法失败,数据驱动测试将失败。 在 测试资源管理器 窗口中选择失败的数据驱动测试时,详细信息窗格将显示数据行索引标识的每个迭代的结果。 在我们的示例中,AddIntegers
算法似乎未正确处理负值。
当测试中的方法得到更正并重新运行测试时,结果栏变为绿色,并将测试方法移动到 通过的测试 组。