通过数据驱动测试,可以使用多个输入数据集运行相同的测试方法。 不必为每个测试用例编写单独的测试方法,只需定义一次测试逻辑,并通过属性或外部数据源提供不同的输入。
概述
MSTest 为数据驱动测试提供了多个属性:
| Attribute | 用例 | 最适用于 |
|---|---|---|
DataRow |
内联测试数据 | 简单静态测试用例 |
DynamicData |
来自方法、属性或字段的数据 | 复杂或计算的测试数据 |
TestDataRow<T> |
使用元数据增强的数据 | 需要显示名称或类别的测试用例 |
DataSource |
外部数据文件或数据库 | 外部数据源的遗留方案 |
ITestDataSource |
自定义数据源属性 | 完全自定义的数据驱动场景 |
小窍门
对于组合测试(测试多个参数集的所有组合),请使用开源 Combinatorial.MSTest NuGet 包。 此社区维护包 在 GitHub 上可用 ,但不受Microsoft维护。
DataRowAttribute
DataRowAttribute 允许您使用多个不同的输入运行相同的测试方法。 将一个或多个 DataRow 属性应用于测试方法,并将其与 TestMethodAttribute 组合在一起。
参数的数量和类型必须与测试方法签名完全匹配。
基本用法
[TestClass]
public class CalculatorTests
{
[TestMethod]
[DataRow(1, 2, 3)]
[DataRow(0, 0, 0)]
[DataRow(-1, 1, 0)]
[DataRow(100, 200, 300)]
public void Add_ReturnsCorrectSum(int a, int b, int expected)
{
var calculator = new Calculator();
Assert.AreEqual(expected, calculator.Add(a, b));
}
}
支持的参数类型
DataRow 支持各种参数类型,包括基元、字符串、数组和 null 值:
[TestClass]
public class DataRowExamples
{
[TestMethod]
[DataRow(1, "message", true, 2.0)]
public void TestWithMixedTypes(int i, string s, bool b, float f)
{
// Test with different primitive types
}
[TestMethod]
[DataRow(new string[] { "line1", "line2" })]
public void TestWithArray(string[] lines)
{
Assert.AreEqual(2, lines.Length);
}
[TestMethod]
[DataRow(null)]
public void TestWithNull(object o)
{
Assert.IsNull(o);
}
[TestMethod]
[DataRow(new string[] { "a", "b" }, new string[] { "c", "d" })]
public void TestWithMultipleArrays(string[] input, string[] expected)
{
// Starting with MSTest v3, two arrays don't need wrapping
}
}
对变量参数使用参数
params使用关键字接受可变数量的参数:
[TestClass]
public class ParamsExample
{
[TestMethod]
[DataRow(1, 2, 3, 4)]
[DataRow(10, 20)]
[DataRow(5)]
public void TestWithParams(params int[] values)
{
Assert.IsTrue(values.Length > 0);
}
}
自定义显示名称
设置属性 DisplayName 以自定义测试用例在测试资源管理器中的显示方式:
[TestClass]
public class DisplayNameExample
{
[TestMethod]
[DataRow(1, 2, DisplayName = "Functional Case FC100.1")]
[DataRow(3, 4, DisplayName = "Edge case: small numbers")]
public void TestMethod(int i, int j)
{
Assert.IsTrue(i < j);
}
}
小窍门
若要更好地控制测试元数据,请考虑将 TestDataRow<T> 与 DynamicData.
TestDataRow<T> 支持显示名称以及测试类别,并忽略单个测试用例的消息。
忽略特定测试用例
从 MSTest v3.8 开始,使用 IgnoreMessage 属性跳过特定数据行:
[TestClass]
public class IgnoreDataRowExample
{
[TestMethod]
[DataRow(1, 2)]
[DataRow(3, 4, IgnoreMessage = "Temporarily disabled - bug #123")]
[DataRow(5, 6)]
public void TestMethod(int i, int j)
{
// Only the first and third data rows run
// The second is skipped with the provided message
}
}
DynamicDataAttribute
DynamicDataAttribute 可用于从方法、属性或字段提供测试数据。 当测试数据复杂、动态计算或对于内联 DataRow 属性过于详细时,请使用此属性。
支持的数据源类型
数据源可以返回任何IEnumerable<T>,其中T是下表列出的类型之一。 实现 IEnumerable<T> 的任何集合都是有效集合,包括 List<T>、数组(如 T[])或自定义集合类型。 根据需求进行选择:
| 返回类型 | 类型安全性 | 元数据支持 | 最适用于 |
|---|---|---|---|
ValueTuple(例如 (int, string)) |
编译时 | 否 | 大多数情境 - 具有全面类型检查的简单语法 |
Tuple<...> |
编译时 | 否 | 当您无法使用ValueTuple时 |
TestDataRow<T> |
编译时 | 是的 | 需要显示名称、类别或忽略消息的测试用例 |
object[] |
仅运行时 | 否 | 遗留代码 - 避免在新测试中使用 |
小窍门
对于新的测试数据方法,在简单情况下请使用 ValueTuple,在需要元数据时请使用 TestDataRow<T>。 避免 object[] 因为它缺少编译时类型检查,并可能导致类型不匹配的运行时错误。
数据源
数据源可以是方法、属性或字段。 这三者都是可互换的- 根据偏好进行选择:
[TestClass]
public class DynamicDataExample
{
// Method - best for computed or yielded data
public static IEnumerable<(int Value, string Name)> GetTestData()
{
yield return (1, "first");
yield return (2, "second");
}
// Property - concise for static data
public static IEnumerable<(int Value, string Name)> TestDataProperty =>
[
(1, "first"),
(2, "second")
];
// Field - simplest for static data
public static IEnumerable<(int Value, string Name)> TestDataField =
[
(1, "first"),
(2, "second")
];
[TestMethod]
[DynamicData(nameof(GetTestData))]
public void TestWithMethod(int value, string name)
{
Assert.IsTrue(value > 0);
}
[TestMethod]
[DynamicData(nameof(TestDataProperty))]
public void TestWithProperty(int value, string name)
{
Assert.IsTrue(value > 0);
}
[TestMethod]
[DynamicData(nameof(TestDataField))]
public void TestWithField(int value, string name)
{
Assert.IsTrue(value > 0);
}
}
注释
数据源的方法、属性和字段必须是 public static 并返回一个受支持类型的 IEnumerable<T>。
小窍门
相关分析器: MSTEST0018 验证数据源是否存在、可访问且具有正确的签名。
来自不同类的数据源
使用类型参数指定其他类:
public class TestDataProvider
{
public static IEnumerable<object[]> GetTestData()
{
yield return new object[] { 1, "first" };
yield return new object[] { 2, "second" };
}
}
[TestClass]
public class DynamicDataExternalExample
{
[TestMethod]
[DynamicData(nameof(TestDataProvider.GetTestData), typeof(TestDataProvider))]
public void TestMethod(int value1, string value2)
{
Assert.IsTrue(value1 > 0);
}
}
自定义显示名称
使用 DynamicDataDisplayName 属性自定义测试用例显示名称:
using System.Reflection;
[TestClass]
public class DynamicDataDisplayNameExample
{
[TestMethod]
[DynamicData(nameof(GetTestData), DynamicDataDisplayName = nameof(GetDisplayName))]
public void TestMethod(int value1, string value2)
{
Assert.IsTrue(value1 > 0);
}
public static IEnumerable<object[]> GetTestData()
{
yield return new object[] { 1, "first" };
yield return new object[] { 2, "second" };
}
public static string GetDisplayName(MethodInfo methodInfo, object[] data)
{
return $"{methodInfo.Name} with value {data[0]} and '{data[1]}'";
}
}
注释
显示名称方法必须是 public static,返回并接受两个 string参数: MethodInfo 和 object[]。
小窍门
若要使用更简单的自定义显示名称方法,请考虑使用 TestDataRow<T> 及其 DisplayName 属性而不是单独的方法。
忽略数据源中的所有测试用例
从 MSTest v3.8 开始,使用 IgnoreMessage 可跳过所有测试用例。
[TestClass]
public class IgnoreDynamicDataExample
{
[TestMethod]
[DynamicData(nameof(GetTestData), IgnoreMessage = "Feature not ready")]
public void TestMethod(int value1, string value2)
{
// All test cases from GetTestData are skipped
}
public static IEnumerable<object[]> GetTestData()
{
yield return new object[] { 1, "first" };
yield return new object[] { 2, "second" };
}
}
小窍门
若要忽略单个测试用例,请使用 TestDataRow<T> 的 IgnoreMessage 属性。 请参阅 TestDataRow<T> 部分。
TestDataRow
该 TestDataRow<T> 类提供对数据驱动测试中测试数据的增强控制。 使用 IEnumerable<TestDataRow<T>> 作为数据源的返回类型进行指定:
- 自定义显示名称:为每个测试用例设置唯一的显示名称
- 测试类别:将元数据附加到单个测试用例
- 忽略消息:跳过具有原因的特定测试用例
- 类型安全数据:对强类型测试数据使用泛型
基本用法
[TestClass]
public class TestDataRowExample
{
[TestMethod]
[DynamicData(nameof(GetTestDataRows))]
public void TestMethod(int value1, string value2)
{
Assert.IsTrue(value1 > 0);
}
public static IEnumerable<TestDataRow> GetTestDataRows()
{
yield return new TestDataRow((1, "first"))
{
DisplayName = "Test Case 1: Basic scenario",
};
yield return new TestDataRow((2, "second"))
{
DisplayName = "Test Case 2: Edge case",
TestCategories = ["HighPriority", "Critical"],
};
yield return new TestDataRow((3, "third"))
{
IgnoreMessage = "Not yet implemented",
};
}
}
DataSourceAttribute
注释
DataSource 仅在 .NET Framework 中可用。 对于 .NET(Core)项目,请使用DataRow或DynamicData。
将 DataSourceAttribute 测试连接到外部数据源,例如 CSV 文件、XML 文件或数据库。
有关详细信息,请参阅:
ITestDataSource
该 ITestDataSource 接口允许你创建完全自定义的数据源属性。 如果需要内置属性不支持的行为,例如基于环境变量、配置文件或其他运行时条件生成测试数据,请实现此接口。
接口成员
该接口定义了两种方法:
| 方法 | 目的 |
|---|---|
GetData(MethodInfo) |
将测试数据返回为 IEnumerable<object?[]> |
GetDisplayName(MethodInfo, object?[]?) |
返回测试用例的显示名称 |
创建自定义数据源属性
若要创建自定义数据源,请定义继承自 Attribute 并实现 ITestDataSource的属性类:
using System.Globalization;
using System.Reflection;
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class MyDataSourceAttribute : Attribute, ITestDataSource
{
public IEnumerable<object?[]> GetData(MethodInfo methodInfo)
{
// Return test data based on your custom logic
yield return [1, "first"];
yield return [2, "second"];
yield return [3, "third"];
}
public string? GetDisplayName(MethodInfo methodInfo, object?[]? data)
{
return data is null
? null
: string.Format(CultureInfo.CurrentCulture, "{0} ({1})", methodInfo.Name, string.Join(",", data));
}
}
[TestClass]
public class CustomDataSourceExample
{
[TestMethod]
[MyDataSource]
public void TestWithCustomDataSource(int value, string name)
{
Assert.IsTrue(value > 0);
Assert.IsNotNull(name);
}
}
实际示例:基于环境的测试数据
此示例显示了一个自定义属性,该属性基于目标框架生成测试数据,并基于作系统进行筛选:
using System.Globalization;
using System.Reflection;
[AttributeUsage(AttributeTargets.Method)]
public class TargetFrameworkDataAttribute : Attribute, ITestDataSource
{
private readonly string[] _frameworks;
public TargetFrameworkDataAttribute(params string[] frameworks)
{
_frameworks = frameworks;
}
public IEnumerable<object?[]> GetData(MethodInfo methodInfo)
{
bool isWindows = OperatingSystem.IsWindows();
foreach (string framework in _frameworks)
{
// Skip .NET Framework on non-Windows platforms
if (!isWindows && framework.StartsWith("net4", StringComparison.Ordinal))
{
continue;
}
yield return [framework];
}
}
public string? GetDisplayName(MethodInfo methodInfo, object?[]? data)
{
return data is null
? null
: string.Format(CultureInfo.CurrentCulture, "{0} ({1})", methodInfo.Name, data[0]);
}
}
[TestClass]
public class CrossPlatformTests
{
[TestMethod]
[TargetFrameworkData("net48", "net8.0", "net9.0")]
public void TestOnMultipleFrameworks(string targetFramework)
{
// Test runs once per applicable framework
Assert.IsNotNull(targetFramework);
}
}
小窍门
对于只需自定义显示名称或添加元数据的简单情况,请考虑使用 TestDataRow<T> 与 DynamicData,而不是实现 ITestDataSource。 为需要动态筛选或复杂数据生成逻辑的方案保留自定义 ITestDataSource 实现。
展开策略
数据驱动测试属性支持该 TestDataSourceUnfoldingStrategy 属性,该属性控制测试用例在测试资源管理器和 TRX 结果中的显示方式。 此属性还确定是否可以独立运行单个测试用例。
可用策略
| 策略 | 行为 |
|---|---|
Auto(默认值) |
MSTest 确定最佳展开策略 |
Unfold |
所有测试用例都会展开并单独显示 |
Fold |
所有测试用例都折叠成单个测试节点 |
何时更改策略
对于大多数方案,默认 Auto 行为提供最佳平衡。 仅当有特定要求时,才考虑更改此设置:
- 非确定性数据源
- MSTest 中的已知限制性或错误
- 许多测试用例的性能问题
示例用法
[TestClass]
public class UnfoldingExample
{
[TestMethod(UnfoldingStrategy = TestDataSourceUnfoldingStrategy.Unfold)] // That's the default behavior
[DataRow(1)]
[DataRow(2)]
[DataRow(3)]
public void TestMethodWithUnfolding(int value, string text)
{
// Each test case appears individually in Test Explorer
}
[TestMethod(UnfoldingStrategy = TestDataSourceUnfoldingStrategy.Fold)]
[DynamicData(nameof(GetData))]
public void TestMethodWithFolding(int value, string text)
{
// All test cases appear as a single collapsed node
}
public static IEnumerable<(int, string)> GetData()
{
yield return (1, "one");
yield return (2, "two");
yield return (3, "three");
}
}
最佳做法
选择正确的属性:使用
DataRow来处理简单的内联数据。 使用DynamicData来处理复杂或计算数据。命名测试用例:利用
DisplayName简化识别测试失败的过程。使数据源保持关闭:尽可能在同一类中定义数据源以提高可维护性。
使用有意义的数据:选择练习边缘事例和边界条件的测试数据。
考虑组合测试:对于测试参数组合,请使用 Combinatorial.MSTest 包。