6


2

Reactive Extensionsを使用したイベントの単体テスト

Reactive Extensions for .NET(Rx)を使用して、イベントを「IObservable」として公開しています。 特定のイベントが発生したことをアサートする単体テストを作成したい。 以下は、テストしたいクラスの簡略版です。

public sealed class ClassUnderTest : IDisposable {

  Subject subject = new Subject();

  public IObservable SomethingHappened {
    get { return this.subject.AsObservable(); }
  }

  public void DoSomething() {
    this.subject.OnNext(new Unit());
  }

  public void Dispose() {
    this.subject.OnCompleted();
  }

}

明らかに、私の実際のクラスはより複雑です。 私の目標は、テスト対象のクラスでいくつかのアクションを実行すると、「IObservable」で通知される一連のイベントが発生することを確認することです。 幸いなことに、テストしたいクラスは IDisposable`を実装し、オブジェクトが破棄されたときにサブジェクトで OnCompleted`を呼び出すと、テストがはるかに簡単になります。

テスト方法は次のとおりです。

// Arrange
var classUnderTest = new ClassUnderTest();
var eventFired = false;
classUnderTest.SomethingHappened.Subscribe(_ => eventFired = true);

// Act
classUnderTest.DoSomething();

// Assert
Assert.IsTrue(eventFired);

変数を使用してイベントが発生したかどうかを判断することはそれほど悪くはありませんが、より複雑なシナリオでは、イベントの特定のシーケンスが発生したことを確認することができます。 イベントを変数に単純に記録してから変数のアサーションを行わなくても可能ですか? 流QなLINQのような構文を使用して「IObservable」でアサーションを作成できると、テストが読みやすくなることを期待しています。

3 Answer


13


この回答は、現在リリースされているRxのバージョン1.0に更新されました。

公式のドキュメントはまだ不十分ですが、MSDNのhttp://msdn.microsoft.com/en-us/library/hh242967%28v=vs.103%29.aspx [観測可能なシーケンスのテストとデバッグ]が出発点として適しています。

テストクラスは、「Microsoft.Reactive」のhttp://msdn.microsoft.com/en-us/library/microsoft.reactive.testing.reactivetest%28v=VS.103%29.aspx [ReactiveTest]から派生する必要があります。 .Testing`名前空間。 このテストは、http://msdn.microsoft.com/en-us/library/microsoft.reactive.testing.testscheduler%28v=VS.103%29.aspx [TestScheduler]に基づいており、仮想時間を提供しますテスト。

TestScheduler.Schedule`メソッドは、仮想時間の特定のポイント(ティック)でアクティビティをキューに入れるために使用できます。 テストは `TestScheduler.Start`によって実行されます。 これは、たとえば、 `ReactiveAssert`クラスを使用してアサートするために使用できる ITestableObserver`を返します。

public class Fixture : ReactiveTest {

  public void SomethingHappenedTest() {
    // Arrange
    var scheduler = new TestScheduler();
    var classUnderTest = new ClassUnderTest();

    // Act
    scheduler.Schedule(TimeSpan.FromTicks(20), () => classUnderTest.DoSomething());
    var actual = scheduler.Start(
      () => classUnderTest.SomethingHappened,
      created: 0,
      subscribed: 10,
      disposed: 100
    );

    // Assert
    var expected = new[] { OnNext(20, new Unit()) };
    ReactiveAssert.AreElementsEqual(expected, actual.Messages);
  }

}

「TestScheduler.Schedule」は、時間20(ティック単位で測定)で「DoSomething」の呼び出しをスケジュールするために使用されます。

次に、「TestScheduler.Start」を使用して、監視可能な「SomethingHappened」で実際のテストを実行します。 サブスクリプションの存続期間は、呼び出しに対する引数によって制御されます(再びティックで測定されます)。

最後に、 ReactiveAssert.AreElementsEqual`を使用して、 OnNext`が予想どおり20時に呼び出されたことを確認します。

このテストでは、「DoSomething」を呼び出すと、すぐに観察可能な「SomethingHappened」が起動されることを確認します。


5


オブザーバブルのこの種のテストは不完全です。 つい最近、RXチームはテストスケジューラといくつかの拡張機能(ライブラリをテストするために内部で使用するBTW)を公開しました。 これらを使用して、何かが起こったかどうかをチェックできるだけでなく、*タイミングと順序*が正しいことを確認することもできます。 ボーナスとして、テストスケジューラを使用すると、「仮想時間」でテストを実行できるため、内部でどれだけ大きな遅延が発生しても、テストは即座に実行されます。

RXチームのジェフリー・ファン・ゴッホhttp://blogs.msdn.com/b/jeffva/archive/2010/08/27/testing-rx.aspx [この種のテストを行う方法に関する記事を公開しました。]

前述のアプローチを使用した上記のテストは、次のようになります。

    [TestMethod]
    public void SimpleTest()
    {
        var sched = new TestScheduler();
        var subject = new Subject();
        var observable = subject.AsObservable();

        var o = sched.CreateHotObservable(
            OnNext(210, new Unit())
            ,OnCompleted(250)
            );
        var results = sched.Run(() =>
                                    {
                                        o.Subscribe(subject);
                                        return observable;
                                    });
        results.AssertEqual(
            OnNext(210, new Unit())
            ,OnCompleted(250)
            );
    }:

編集:.OnNext(または他のメソッド)を暗黙的に呼び出すこともできます:

        var o = sched.CreateHotObservable(OnNext(210, new Unit()));
        var results = sched.Run(() =>
        {
            o.Subscribe(_ => subject.OnNext(new Unit()));
            return observable;
        });
        results.AssertEqual(OnNext(210, new Unit()));

私のポイントは-最も単純な状況では、イベントが発生することを確認するだけです(f.e. Whereが正しく機能していることを確認しています)。 しかし、その後、複雑さが増すにつれて、タイミング、完了、または仮想スケジューラーを必要とする何かのテストを開始します。 しかし、「通常の」テストとは反対に、仮想スケジューラを使用したテストの性質は、「アトミック」操作ではなく、オブザーバブル全体を一度にテストすることです。

だから、おそらくあなたは道のどこかで仮想スケジューラに切り替える必要があります-最初から始めてみませんか?

P.S. また、テストケースごとに異なるロジックに頼る必要があります-f.e. あなたは、何かが起こったのとは反対に何かが起こっていないことをテストするために、非常に異なる観測可能性を持っているでしょう。


0


もっと流fluentかどうかは定かではありませんが、変数を導入せずにこれを行うことができます。

var subject = new Subject();
subject
    .AsObservable()
    .Materialize()
    .Take(1)
    .Where(n => n.Kind == NotificationKind.OnCompleted)
    .Subscribe(_ => Assert.Fail());

subject.OnNext(new Unit());
subject.OnCompleted();