问题描述
.NET应用程序中有很多测试。其中一些测试与时间相关,一些代码与.NET TPL是多线程的。几个星期以来,我们遇到了很多问题。以前,每次成功运行测试。
我举了一个带有简单计时器的示例:
在类NotSoParallelOps方法ScheduleExecution(L107)中,将启动常规System.Threading.Timer
。相应的测试在NotSoParallelOpsTests方法ScheduleExecutionWithStop(L79)中,该计时器将在其中启动计时器,测试线程被阻塞一段时间,然后如果计时器已过,将对其进行评估。
TPL的另一个示例:
public class Foo
{
public void Start(int delay)
{
Task.Run(async delegate
{
await Task.Delay(delay);
TaskDone?.Invoke(this,EventArgs.Empty);
});
}
public event EventHandler TaskDone;
}
[TestFixture]
public class FooTests
{
[Test]
public void TestRunner()
{
// Arrange
var foo = new Foo();
var resetEvent = new ManualResetEvent(false);
foo.TaskDone += delegate
{
resetEvent.Set();
};
// Act
foo.Start(1000);
var result = resetEvent.WaitOne(2000);
// Assert
Assert.IsTrue(result,"Task not done in time");
}
}
新任务将从Task.Run
开始。由于某些原因,任务被延迟,然后引发事件。测试将注册到事件并检查任务是否及时执行。
如果使用正在使用NUnit测试运行程序的Visual Studio Resharper UnitTest Explorer
执行测试,则测试大部分会成功。如果将使用nunit-console,则测试为failing very often,但也为succeed sometimes。不仅在本地计算机上,在GitHub Workflow运行程序和GitLab CI运行程序上也是如此。同样的问题,同样的结果。
一个想法是更改等待计时器测试的时间。它有效,但是为什么现在要这样做呢?测试成功进行了几个月/几年。同样,构建基础架构也变得越来越快,性能更高。
我对Task.Run
测试应用了相同的想法。但是,即使我将时间设置为60秒,它也不会运行。最令人困惑的是,该测试在我的本地计算机上运行正常,但在GitHub Workflow运行程序和GitLab CI运行程序上却无法正常运行。
有人知道我该怎么做才能解决此问题,或者有更好的主意来测试这种代码行吗?
- NUnit控制台:3.11.1
- NUnit TestAdapter:3.17.0
解决方法
您可以尝试使用TaskCompletionSource
和CancellationToken
重写测试。
如果您不需要EventArgs
,则测试的重写版本将如下所示:
[Test]
public async Task TestRunner()
{
// Arrange
var foo = new Foo();
var fooTaskCompleted = new TaskCompletionSource<object>();
var timeoutPolicy = new CancellationTokenSource(2000);
timeoutPolicy.Token.Register((future) => ((TaskCompletionSource<object>)future).TrySetCanceled(),useSynchronizationContext: false,state: fooTaskCompleted);
foo.TaskDone += delegate
{
fooTaskCompleted.SetResult(null);
};
// Act
foo.Start(1000);
await fooTaskCompleted.Task;
// Assert
Assert.IsFalse(fooTaskCompleted.Task.IsCanceled);
Assert.IsFalse(fooTaskCompleted.Task.IsFaulted);
}
如果您需要EventArgs
,则可以这样重写TestRunner
方法:
[Test]
public async Task TestRunner()
{
// Arrange
var foo = new Foo();
var fooTaskCompleted = new TaskCompletionSource<EventArgs>();
var timeoutPolicy = new CancellationTokenSource(2000);
timeoutPolicy.Token.Register((future) => ((TaskCompletionSource<EventArgs>)future).TrySetCanceled(),state: fooTaskCompleted);
foo.TaskDone += (s,e) =>
{
fooTaskCompleted.TrySetResult(e);
};
// Act
foo.Start(1000);
var eventArgs = await fooTaskCompleted.Task;
// Assert
Assert.IsFalse(fooTaskCompleted.Task.IsCanceled);
Assert.IsFalse(fooTaskCompleted.Task.IsFaulted);
}