XUnit实现:同组测试因指定异常失败时跳过后续测试
XUnit 分组失败批量标记测试为失败的实现方案
核心思路
通过自定义Fact特性结合XunitTestCase拦截测试执行流程,用线程安全集合记录已失败的分组:
- 测试执行前检查分组状态,若分组已失败则直接标记当前测试为失败,跳过实际逻辑执行
- 测试执行后若失败且异常类型匹配,则标记对应分组为失败,后续同组测试直接触发失败逻辑
完整实现代码
using System; using System.Collections.Concurrent; using System.Reflection; using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; using Xunit.Sdk; // 自定义分组失败Fact特性 [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class FactInFailureGroupAttribute : FactAttribute { public string GroupName { get; } public Type FailureExceptionType { get; } public FactInFailureGroupAttribute(string groupName, Type failureExceptionType) { GroupName = groupName ?? throw new ArgumentNullException(nameof(groupName)); FailureExceptionType = failureExceptionType ?? throw new ArgumentNullException(nameof(failureExceptionType)); if (!typeof(Exception).IsAssignableFrom(failureExceptionType)) throw new ArgumentException("指定类型必须是Exception的派生类", nameof(failureExceptionType)); } public override IXunitTestCase CreateTestCase(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod) { return new FailureGroupTestCase( discoveryOptions, testMethod, GroupName, FailureExceptionType ); } } // 自定义TestCase处理分组失败逻辑 public class FailureGroupTestCase : XunitTestCase { private static readonly ConcurrentHashSet<string> _failedGroups = new ConcurrentHashSet<string>(); private string _groupName; private Type _failureExceptionType; // 序列化所需的默认构造函数 public FailureGroupTestCase() { } public FailureGroupTestCase( ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, string groupName, Type failureExceptionType) : base(discoveryOptions, testMethod) { _groupName = groupName; _failureExceptionType = failureExceptionType; } public override async Task<RunSummary> RunAsync( IMessageSink diagnosticMessageSink, IMessageBus messageBus, object[] constructorArguments, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource) { // 执行前检查:分组已失败则直接标记测试失败 if (_failedGroups.Contains(_groupName)) { var failureMsg = $"分组 '{_groupName}' 已因 {_failureExceptionType.Name} 失败,跳过测试执行"; var testFailedMsg = new TestFailed( TestCase, DisplayName, SkipReason, failureMsg, null, Array.Empty<object>() ); messageBus.QueueMessage(testFailedMsg); return new RunSummary { Total = 1, Failed = 1 }; } // 正常执行测试 var runSummary = await base.RunAsync(diagnosticMessageSink, messageBus, constructorArguments, aggregator, cancellationTokenSource); // 执行后检查:测试失败且异常匹配则标记分组为失败 if (runSummary.Failed > 0) { var exception = aggregator.ToException(); if (exception != null && _failureExceptionType.IsAssignableFrom(exception.GetType())) { _failedGroups.Add(_groupName); } } return runSummary; } // 序列化/反序列化逻辑,保证分组信息在测试序列化场景下正常工作 public override void Serialize(IXunitSerializationInfo data) { base.Serialize(data); data.AddValue("GroupName", _groupName); data.AddValue("FailureExceptionType", _failureExceptionType); } public override void Deserialize(IXunitSerializationInfo data) { base.Deserialize(data); _groupName = data.GetValue<string>("GroupName"); _failureExceptionType = data.GetValue<Type>("FailureExceptionType"); } }
使用示例
// 假设ConnectionException是自定义的连接失败异常类型 [FactInFailureGroup("ConnectsToBlah", typeof(ConnectionException))] public void TestOne() { // 外部服务器连接测试逻辑 } [FactInFailureGroup("ConnectsToBlah", typeof(ConnectionException))] public void TestTwo() { // 外部服务器连接测试逻辑 }
关键说明
- 线程安全:使用
ConcurrentHashSet存储失败分组,适配XUnit默认的并行测试执行模式,避免多线程冲突 - 测试状态控制:通过拦截
RunAsync方法,直接发送TestFailed消息标记测试失败,而非跳过,满足需求中"标记为失败"的要求 - 序列化支持:实现
Serialize/Deserialize方法,确保在XUnit需要序列化测试用例的场景(如分布式测试)下,分组信息不会丢失
内容的提问来源于stack exchange,提问作者Darren Oakey
相关产品推荐
相关产品推荐

