2

Reactive Extensions には、非同期メソッドの呼び出しを簡素化するための魅力的な小さなフックがあります。

var func = Observable.FromAsyncPattern<InType, OutType>(
    myWcfService.BeginDoStuff,
    myWcfService.EndDoStuff);

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

これを WPF プロジェクトで使用していますが、実行時にうまく機能します。

残念ながら、この手法を使用するメソッドを単体テストしようとすると、ランダムなエラーが発生します。このコードを含むテストの 5 回の実行ごとに ~3 回失敗します。

サンプル テストは次のとおりです (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 にブレークポイントを設定し、ブレークポイントに到達しないことを観察します。さらに、DoMethodThatInvokesService (非同期呼び出しを開始する) を呼び出した後、長い遅延を強制することができますが、コードはまだ実行されません。Rx フレームワークを呼び出すコード行が呼び出されたことは知っています

私が試した他のこと:

  • ここでの提案に従って、最後から 2 番目の行を変更しようとしまし

  • .Take(1)次のように Rx コードに追加しました。

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

これにより、失敗率が 5 分の 1 程度に改善されましたが、それでも発生しました。

  • 単純なジェーン非同期パターンを使用するように Rx コードを書き直しました。これは機能しますが、私の開発者のエゴは、退屈な古い開始/終了の代わりに Rx を使用したいと思っています。

最終的には回避策 (つまり、Rx を使用しない) がありますが、それは理想的ではないと感じています。誰かが過去にこの問題に遭遇し、解決策を見つけた場合は、ぜひ聞いてください.

更新

Rx フォーラムにも投稿しましたが、今後のリリースにはテスト スケジューラが含まれる予定です。それが利用可能になったら、おそらくそれが究極の解決策になるでしょう。

4

2 に答える 2

6

問題は、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 コードを再びテスト可能にします。

于 2010-07-09T05:18:30.823 に答える
3

この問題は、 によってスケジュールされたコールの非同期性が原因で発生しObserveOnDispatcherます。テストが終了するまでにすべてが完了することを保証することはできません。したがって、スケジューリングを自分の管理下に置く必要があります。

スケジューラをクラスに注入するのはどうですか?

次に、 を呼び出すのではなく、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);
   }
}

次に、実行時に UISafeScheduler を DispatcherScheduler で初期化し、テストで偽のスケジューラで初期化します。

于 2010-06-11T10:12:37.657 に答える