我在测试使用基于任务的服务的反应式代码时遇到问题。 在我被测试的类中,我使用任务并使用ToObservable来做反应性的事情。
public void Method()
{
_svc.MyTaskServiceMethod().ToObservable().Select(....) //pipe it elsewhere and do interesting things.
}
现在在单元测试中,我正在测试一些时间(使用最小起订量进行服务)
svcMock.Setup(x => x.MyTaskServiceMethod()).Returns(() =>
Observable.Return("VALUE", testScheduler)
.Delay(TimeSpan.FromMilliseconds(100), testScheduler)
.ToTask()
);
问题在于,尽管在返回/延迟调用中使用了测试计划程序,但任务本身仍在单独的线程上完成。 我通过在代码中添加当前托管线程 ID 的几个控制台写入来看到这一点。
svcMock.Setup(x => x.MyServiceMethod()).Returns(() =>
{
var task = Observable.Return("VALUE", testScheduler)
.Delay(TimeSpan.FromMilliseconds(1000), testScheduler)
.Do(x => { Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString() + " Obs"); })
.ToTask();
task.ContinueWith((_) =>
{
Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString() + " Task");
});
return task;
});
Do(..) 在主测试线程上执行,并且在 testSchduler.AdvanceBy(..) 调用后完全按照我的预期发生。
任务延续仍在单独的线程中进行,并且基本上直到单元测试的主体完成之后才会执行。 因此,在我的目标正文中,没有什么能真正完成我的任务。ToObservable() observable。
任务延续将使用任务池线程,因此您的延续将逃脱测试计划程序的控制。如果指定选项 TaskContinuationOptions.ExecuteSynchronously
,它将使用相同的线程,结果将根据需要发布到可观察量:
task.ContinueWith((_) =>
{
Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString() + " Task");
}, TaskContinuationOptions.ExecuteSynchronously);
补遗
您可能会发现 Rx 站点上的这个相关讨论非常有启发性地介绍了 TPL 中的并发性 -> Rx 转换,尤其是ToObservable()
。
前段时间,我与人合著了一个基于 NUnit 的单元测试库,以帮助精确地进行 Rx 和 TPL 测试。为此,我们构建了一个测试 TPL 调度器来强制所有 TPL 任务在没有并发的情况下运行。您可以在此处查看相关代码:https://github.com/Testeroids/Testeroids/blob/master/solution/src/app/Testeroids/TplTestPlatformHelper.cs#L87