我正在测试驱动一个注入了大量工作任务的类,异步运行并在完成后重新启动它们,直到被告知停止所有任务。
因为我首先要做测试,所以我需要编写一个强制我编写重启逻辑的测试,并且我已经成功完成了这项工作,但我认为我做得不是很好。
测试代码:( FakeTask
基本上是一个测试间谍,可以跟踪它是否被调用以及有多少次)
[Fact]
public async void Start_GivenTask_RerunsTaskUntilStopped()
{
var agent = CreateKlarnaAgent();
var fakeTask = DoNothingTask();
agent.Start(fakeTask);
Thread.Sleep(500);
await agent.Stop();
Assert.True(fakeTask.TimesRun > 1);
}
(相关)生产代码:
public void Start(params IWorkTask[] workTasks)
{
_logWriter.Debug("Starting...");
_tasks = workTasks
.Select(workTask => workTask.DoWork().ContinueWith(task => OnTaskComplete(task, workTask)))
.ToArray();
}
private void OnTaskComplete(Task completedTask, IWorkTask workTask)
{
if (completedTask.IsFaulted)
{
foreach (var exception in completedTask.Exception.InnerExceptions)
{
_logWriter.Error("Unhandled exception thrown!", exception);
}
}
else workTask.DoWork().ContinueWith(task => OnTaskComplete(task, workTask));
}
public Task Stop()
{
return Task.WhenAll(_tasks)
.ContinueWith(t => { _logWriter.Debug("Stopped"); });
}
现在测试真的取决于竞争条件,并且它根本不像单元测试。我如何摆脱Thread.Sleep(500)
电话?或者这只是我应该在集成测试中测试的东西?
答案 0 :(得分:2)
另一方面,我建议不要一般性地编写“任务运行者”,并建议特别针对ContinueWith
,因为它是如此危险的API。
在我看来,使用“重复”循环和“取消”取消令牌可以更清楚地表达“重复永远直到取消”逻辑:
static async Task WorkAsync(Func<Task> doWork, CancellationToken token)
{
while (true)
{
await doWork();
token.ThrowIfCancellationRequested();
}
}
但是,如果你想按原样对“任务跑步者”进行单元测试,你需要让你的FakeTask
更加聪明。例如,您可以让它在达到给定计数时设置信号并让您的单元测试等待:
class FakeTask : IWorkTask
{
private readonly TaskCompletionSource<object> _done = new TaskCompletionSource<object>();
public Task Done { get { return _done.Task; } }
public Task DoWork()
{
++TimesRun;
if (TimesRun > 1)
_done.TrySetResult(null);
return Task.CompletedTask;
}
}
[Fact]
public async Task Start_GivenTask_RerunsTaskUntilStopped()
{
var agent = CreateKlarnaAgent();
var fakeTask = DoNothingTask();
agent.Start(fakeTask);
await fakeTask.Done;
await agent.Stop();
Assert.True(fakeTask.TimesRun > 1); // spurious test at this point
}