由于计时和线程问题,NUnit 测试执行失败
NUnit test execution fails because of timing and threading problems
我们在 .NET 应用程序中进行了大量测试。其中一些测试与时间相关,一些代码与 .NET TPL 是多线程的。几个星期以来,我们遇到了很多问题。之前,测试 运行ning 每次都成功。
我用一个简单的计时器来举例:
在定时器启动的class NotSoParallelOps Method ScheduleExecution (L107) a normal System.Threading.Timer
will be started. The corresponding tests are in 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
开始。由于某些原因,任务被延迟,然后引发了一个事件。测试注册到事件并检查任务是否及时执行。
如果测试是使用 Visual Studio Resharper UnitTest Explorer
执行的,它使用的是 NUnit 测试 运行ner,测试大多是成功的。如果将使用 nunit-console,则测试为 failing very often but also succeed sometimes。不仅在本地机器上,在 GitHub Workflow 运行 用户和 GitLab CI 运行 用户上也是如此。同样的问题,同样的结果。
一个想法是更改等待计时器测试的时间。它有效,但为什么现在要这样做?几个 month/years 的测试是成功的。此外,构建基础架构变得更快、性能更高。
我将同样的想法应用到 Task.Run
测试中。但它不会 运行 即使我将时间设置为 60 秒。最让人困惑的是,测试在我的本地机器上运行良好,但在 GitHub Workflow 运行ners 和 GitLab CI 运行ners.
有谁知道我可以做些什么来解决这个问题,或者有更好的办法来测试这些代码行吗?
- NUnit 控制台:3.11.1
- NUnit 测试适配器: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(), useSynchronizationContext: false, 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);
}
我们在 .NET 应用程序中进行了大量测试。其中一些测试与时间相关,一些代码与 .NET TPL 是多线程的。几个星期以来,我们遇到了很多问题。之前,测试 运行ning 每次都成功。
我用一个简单的计时器来举例:
在定时器启动的class NotSoParallelOps Method ScheduleExecution (L107) a normal System.Threading.Timer
will be started. The corresponding tests are in 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
开始。由于某些原因,任务被延迟,然后引发了一个事件。测试注册到事件并检查任务是否及时执行。
如果测试是使用 Visual Studio Resharper UnitTest Explorer
执行的,它使用的是 NUnit 测试 运行ner,测试大多是成功的。如果将使用 nunit-console,则测试为 failing very often but also succeed sometimes。不仅在本地机器上,在 GitHub Workflow 运行 用户和 GitLab CI 运行 用户上也是如此。同样的问题,同样的结果。
一个想法是更改等待计时器测试的时间。它有效,但为什么现在要这样做?几个 month/years 的测试是成功的。此外,构建基础架构变得更快、性能更高。
我将同样的想法应用到 Task.Run
测试中。但它不会 运行 即使我将时间设置为 60 秒。最让人困惑的是,测试在我的本地机器上运行良好,但在 GitHub Workflow 运行ners 和 GitLab CI 运行ners.
有谁知道我可以做些什么来解决这个问题,或者有更好的办法来测试这些代码行吗?
- NUnit 控制台:3.11.1
- NUnit 测试适配器: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(), useSynchronizationContext: false, 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);
}