如何为NUnit测试设置默认超时?Azure DevOps流水线超时问题求助
问题描述
Aborting test run: test run timeout of 2700000 milliseconds exceeded
Azure DevOps流水线时常因上述超时提示失败,部分测试耗时超出预期但无法定位具体测试。需求如下:
- 为所有NUnit测试设置2分钟默认超时,无需逐个添加
Timeout属性 - 新增测试自动应用该超时规则
- 超时后立即标记测试失败
调研发现NUnit原生不支持全局默认超时,尝试过在SetUp/TearDown中用System.Timers.Timer的方案,但超2分钟后无法立即触发失败,代码如下:
System.Timers.Timer timer; public System.Timers.Timer Timer { get { if (timer == null) { timer = new System.Timers.Timer(); timer.Interval = 120000; timer.Elapsed += Timer_Elapsed; } return timer; } } void Timer_Elapsed(object sender, ElapsedEventArgs e) { Timer.Stop(); Assert.Fail("Timeout"); } [SetUp] public void Setup() { Timer.Start(); } [TearDown] public void TernDown() { Timer.Stop(); }
解决方案
方案1:全局超时特性(推荐)
自定义TestActionAttribute实现全局注入,无需修改现有测试代码,程序集级别生效后所有测试自动继承超时规则:
using NUnit.Framework; using NUnit.Framework.Interfaces; using System; using System.Threading; [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)] public class GlobalTimeoutAttribute : TestActionAttribute { private readonly int _timeoutMs; private CancellationTokenSource _cts; public GlobalTimeoutAttribute(int timeoutMs = 120000) { _timeoutMs = timeoutMs; } public override void BeforeTest(ITest test) { _cts = new CancellationTokenSource(_timeoutMs); _cts.Token.Register(() => { throw new TestTimeoutException($"测试超时,已超过{_timeoutMs/1000}秒"); }); } public override void AfterTest(ITest test) { _cts?.Dispose(); } public override ActionTargets Targets => ActionTargets.Test; } public class TestTimeoutException : Exception { public TestTimeoutException(string message) : base(message) { } }
在AssemblyInfo.cs中添加程序集标记,全局启用:
[assembly: GlobalTimeout(120000)] // 2分钟超时
方案2:基类封装超时逻辑
创建测试基类,所有测试类继承该基类,同步/异步测试都支持:
using NUnit.Framework; using System; using System.Threading; using System.Threading.Tasks; public class TestBase { protected void RunWithTimeout(Action testAction, int timeoutMs = 120000) { using var cts = new CancellationTokenSource(timeoutMs); try { Task.Run(testAction, cts.Token).Wait(cts.Token); } catch (AggregateException ex) { if (ex.InnerException is OperationCanceledException) { Assert.Fail($"测试超时,已超过{timeoutMs/1000}秒"); } throw; } } protected async Task RunWithTimeout(Func<Task> testAction, int timeoutMs = 120000) { using var cts = new CancellationTokenSource(timeoutMs); try { await Task.Run(testAction, cts.Token); } catch (OperationCanceledException) { Assert.Fail($"测试超时,已超过{timeoutMs/1000}秒"); } } } // 测试类示例 [TestFixture] public class MyTests : TestBase { [Test] public void SyncLongTest() { RunWithTimeout(() => { Thread.Sleep(130000); // 模拟超时场景 }); } [Test] public async Task AsyncLongTest() { await RunWithTimeout(async () => { await Task.Delay(130000); // 模拟超时场景 }); } }
方案对比
- 方案1:零侵入,程序集级别全局生效,新增测试自动继承,超时后立即抛出异常终止测试,能准确定位超时测试,是最优解。
- 方案2:需要测试类继承基类,同步/异步测试都兼容,灵活性较强但有一定侵入性。
内容的提问来源于stack exchange,提问作者Dilshod K
相关产品推荐
相关产品推荐

