代码之家  ›  专栏  ›  技术社区  ›  Andrew Anderson

使用FromAsyncPattern进行单元测试

  •  2
  • Andrew Anderson  · 技术社区  · 16 年前

    反应式扩展有一个有趣的小钩子来简化调用异步方法:

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

    我在一个WPF项目中使用它,它在运行时工作得很好。

    不幸的是,在尝试使用这种技术的单元测试方法时,我遇到了随机失败。大约每执行五次包含此代码的测试中有三次失败。

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

    [TestMethod()]
    public void SomeTest()
    {
       // arrange
       var container = GetAutoMockingContainer();
    
       container.Resolve<IMyWcfServiceClient>()
          .Expect(x => x.BeginDoStuff(null, null, null))
          .IgnoreArguments()
          .Do(
             new Func<Specification, AsyncCallback, object, IAsyncResult>((inData, asyncCallback, state) =>
                {
                   return new CompletedAsyncResult(asyncCallback, state);
                 }));
    
       container.Resolve<IRepositoryServiceClient>()
          .Expect(x => x.EndDoStuff(null))
          .IgnoreArguments()
          .Do(
             new Func<IAsyncResult, OutData>((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中设置断点并观察它们从未到达来验证这一点。此外,我可以在调用调用调用服务(它启动异步调用)的domethodthatinvokesseservice之后强制长时间延迟,代码仍然不会运行。我 做 知道调用RX框架的代码行是被调用的。

    我尝试过的其他事情:

    这使我的失败率提高到了1/5,但仍然发生了。

    • 我已经重写了RX代码以使用普通的Jane异步模式。这是可行的,但是我的开发人员非常希望使用RX而不是无聊的旧的开始/结束。

    最后,我确实手头有一份工作(即不要使用RX),但我觉得这并不理想。如果过去有人碰到这个问题并找到了解决办法,我很想听听。

    更新 :

    我也 posted on the Rx forums 它们将包括一个测试调度程序和一个即将发布的版本。这可能是最终的解决方案,一旦它可用。

    2 回复  |  直到 10 年前
        1
  •  3
  •   Samuel Jack    16 年前

    该问题是由调度的调用的异步性质引起的。 ObserveOnDispatcher . 你不能保证在你的测试结束时全部完成。所以你需要把日程安排在你的控制之下。

    如何将调度程序注入到类中?

    然后,而不是打电话 观察到的调度员 你打电话 ObserveOn ,通过 IScheduler 注入的实现。

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

    如果您不喜欢在使用RX的任何地方都必须插入一个调度程序的想法,那么如何创建自己的扩展方法,类似这样的方法(前面未测试的代码):

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

    然后在运行时,用DispatcherScheduler初始化uisafeScheduler,在测试中,用假调度程序初始化它。

        2
  •  6
  •   NSGaga-mostly-inactive    10 年前

    问题是mstest.exe运行一个调度程序(即dispatcher.current!)=空),所以ObserveOnDispatcher工作。但是,这个调度员什么也不做!(即队列调度程序项将被忽略)您编写的任何显式使用schedule的代码。调度程序不稳定

    我用暴力解决了这个问题 ReactiveUI -以下是重要的部分:

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

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

    然后,在实现iobservable的类中,都采用isscheduler参数,其默认值最终将成为全局默认调度程序。我本可以做得更好,但这对我很有用,使ViewModel代码可以再次测试。