使用FromAsyncPattern进行unit testing

Reactive Extensions有一个性感的小钩子来简化调用异步方法:

var func = Observable.FromAsyncPattern( myWcfService.BeginDoStuff, myWcfService.EndDoStuff); func(inData).ObserveOnDispatcher().Subscribe(x => Foo(x)); 

我在WPF项目中使用它,它在运行时运行良好。

不幸的是,当尝试使用这种技术的unit testing方法时,我遇到了随机故障。 包含此代码的测试的每五次执行中约有3次失败。

这是一个示例测试(使用Rhino / unity自动模拟容器实现):

 [TestMethod()] public void SomeTest() { // arrange var container = GetAutoMockingContainer(); container.Resolve() .Expect(x => x.BeginDoStuff(null, null, null)) .IgnoreArguments() .Do( new Func((inData, asyncCallback, state) => { return new CompletedAsyncResult(asyncCallback, state); })); container.Resolve() .Expect(x => x.EndDoStuff(null)) .IgnoreArguments() .Do( new Func((ar) => { return someMockData; })); // act var target = CreateTestSubject(container); target.DoMethodThatInvokesService(); // Run the dispatcher for everything over background priority Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Background, new Action(() => { })); // assert Assert.IsTrue(my operation ran as expected); } 

我看到的问题是,我指定在异步操作完成时运行的代码(在本例中为Foo(x))永远不会被调用。 我可以通过在Foo中设置断点并观察它们永远不会到达来validation这一点。 此外,我可以在调用DoMethodThatInvokesService(启动异步调用)后强制延迟很长时间,但代码仍然没有运行。 我知道调用Rx框架的代码行被调用了。

我试过的其他事情:

  • 我试图根据这里的建议修改第二行: Reactive Extensions Rx – 用ObserveOnDispatcher测试一些东西没有爱。

  • 我已经添加了.Take(1)到Rx代码,如下所示:

    func(inData).ObserveOnDispatcher()。Take(1).Subscribe(x => Foo(x));

这将我的失败率提高到了五分之一,但它们仍然存在。

  • 我已经重写了Rx代码以使用普通的jane异步模式。 这是有效的,但是我的开发者自我真的很想使用Rx而不是厌倦旧的开始/结束。

最后我手边有一个工作(即不要使用Rx),但我觉得它并不理想。 如果有人在过去遇到过这个问题并找到了解决办法,我非常希望能听到这个问题。

更新

我还发布了Rx论坛 ,他们将包括一个即将发布的测试调度程序。 一旦可用,这可能是最终的解决方案。

问题是由ObserveOnDispatcher调度的调用的异步性质引起的。 您无法保证在测试结束时它们全部完成。 因此,您需要在您的控制下进行调度。

如何将调度程序注入您的课程?

然后,调用ObserveOnDispatcher而不是调用ObserveOnDispatcher ,传入注入的IScheduler实现。

在运行时,您将注入DispatcherScheduler ,但在测试中,您将注入一个伪调度程序,该调度程序将对其给出的所有操作进行排队,并在您的测试控制的时间运行它们。

如果你不喜欢在你使用Rx的地方注入调度程序的想法,那么如何创建自己的扩展方法,如下所示(未经测试的代码):

 public static MyObservableExtensions { public static IScheduler UISafeScheduler {get;set;} public static IObservable ObserveOnUISafeScheduler(this IObservable source) { if (UISafeScheduler == null) { throw new InvalidOperation("UISafeScheduler has not been initialised"); } return source.ObserveOn(UISafeScheduler); } } 

然后在运行时,使用DispatcherScheduler初始化UISafeScheduler,并在测试中使用您的假调度程序对其进行初始化。

问题是MSTest.exe运行Dispatcher(即Dispatcher.Current!= null),因此ObserveOnDispatcher可以正常工作。 但是,这个Dispatcher什么都不做! (即将忽略排队的调度程序项)您编写的明确使用Schedule.Dispatcher的任何代码都是不可测试的

我在ReactiveUI中通过暴力解决了这个问题 – 这是重要的一点:

https://github.com/reactiveui/ReactiveUI/blob/master/ReactiveUI/RxApp.cs#L99

我们基本上设置了一个定义默认调度程序的全局变量,然后我们尝试检测我们何时进入测试运行器。

然后,在我们实现IObservable的类中,都采用IScheduler参数,其默认值最终将成为全局默认调度程序。 我可能做得更好,但这适用于我并使ViewModel代码再次可测试。