如何在MSTest数据驱动测试中混合使用不同断言类型
MSTest数据驱动测试整合正常场景与异常场景
核心结论
完全可以把正常返回断言和异常断言整合到同一个DynamicData数据驱动测试中,同时实现用例与测试代码的解耦。
实现思路
- 封装测试用例模型:创建类来统一管理每个测试用例的输入、预期结果(正常场景)、预期异常类型(异常场景),实现用例的独立维护。
- 独立维护测试数据源:把所有正常/异常场景的用例整理成静态数据源,与测试逻辑分离。
- 编写统一测试逻辑:在单个测试方法中,根据用例配置自动判断执行正常结果断言还是异常断言。
具体实现代码
1. 定义测试用例模型
public class ApprovalCreationTestCase { // 用例名称,方便测试时识别场景 public string TestCaseName { get; set; } // 用于创建测试输入对象的工厂方法,避免用例间状态污染 public Func<Approval> ApprovalFactory { get; set; } // 正常场景下的预期最小ID(对应原代码中ID>0的断言) public long ExpectedMinValidId { get; set; } = 0; // 异常场景下的预期异常类型,正常场景设为null public Type ExpectedExceptionType { get; set; } }
2. 准备静态测试用例数据源
public static IEnumerable<object[]> ApprovalCreationTestCases { get { // 正常场景:有效用户+合法按钮文本 yield return new object[] { new ApprovalCreationTestCase { TestCaseName = "正常创建审批-有效用户与按钮", ApprovalFactory = () => { var approval = CreateSendToApproverTestCase(); approval.SignTicketId = signTicketId; // 假设signTicketId为测试类成员变量 approval.AssignedTo = "alice ; "; approval.Buttons[0].Text = "默认按钮"; // 恢复合法按钮文本 return approval; }, ExpectedMinValidId = 0, ExpectedExceptionType = null } }; // 异常场景1:用户不存在 yield return new object[] { new ApprovalCreationTestCase { TestCaseName = "创建审批失败-指定用户不存在", ApprovalFactory = () => { var approval = CreateSendToApproverTestCase(); approval.SignTicketId = signTicketId; approval.AssignedTo = "alice ; aabbb "; return approval; }, ExpectedExceptionType = typeof(UserNotExistException) } }; // 异常场景2:用户为空 yield return new object[] { new ApprovalCreationTestCase { TestCaseName = "创建审批失败-用户为空字符串", ApprovalFactory = () => { var approval = CreateSendToApproverTestCase(); approval.SignTicketId = signTicketId; approval.AssignedTo = ""; return approval; }, ExpectedExceptionType = typeof(NullUserException) } }; // 异常场景3:按钮文本非法 yield return new object[] { new ApprovalCreationTestCase { TestCaseName = "创建审批失败-按钮文本格式非法", ApprovalFactory = () => { var approval = CreateSendToApproverTestCase(); approval.SignTicketId = signTicketId; approval.AssignedTo = "alice"; approval.Buttons[0].Text = "__Cancel"; return approval; }, ExpectedExceptionType = typeof(ButtonTextException) } }; } }
3. 编写统一的测试方法
[TestClass] public class ApprovalServiceTests { private IApprovalService _approvalService; private long signTicketId; // 可在TestInitialize中初始化依赖 [TestInitialize] public void TestInitialize() { _approvalService = new ApprovalService(); // 实例化待测试服务 // 初始化signTicketId等测试依赖数据 } [TestMethod] [DynamicData(nameof(ApprovalCreationTestCases), DynamicDataSourceType.Property)] public void CreateApproval_ShouldBehaveAsExpected(ApprovalCreationTestCase testCase) { var approval = testCase.ApprovalFactory(); if (testCase.ExpectedExceptionType != null) { // 异常场景:断言抛出指定类型的异常 Assert.ThrowsException(testCase.ExpectedExceptionType, () => _approvalService.CreateApproval(approval, false)); } else { // 正常场景:执行方法并验证返回结果 var newApproval = _approvalService.CreateApproval(approval); Assert.IsTrue(newApproval.SignTicketApprovalId.ToLong() > testCase.ExpectedMinValidId); Thread.Sleep(3000); // 原代码中的等待逻辑,如需灵活控制可添加到用例模型 } } // 原有的测试对象初始化方法 private Approval CreateSendToApproverTestCase() { return new Approval { Buttons = new List<Button> { new Button { Text = "默认按钮" } } // 其他必要属性初始化 }; } }
关键细节说明
- 用例解耦:所有测试场景都集中在
ApprovalCreationTestCases静态属性中,后续新增/修改用例只需调整数据源,无需修改测试逻辑。 - 状态隔离:用
Func<Approval>工厂方法创建输入对象,确保每个用例使用独立的实例,避免不同用例间的状态污染。 - 可读性提升:每个用例设置
TestCaseName,测试运行时可直接看到场景名称,便于快速定位问题。
内容的提问来源于stack exchange,提问作者qwer11121
相关产品推荐
相关产品推荐

