使用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代码再次可测试。