監視可能シーケンスのテストとデバッグ

Rx アプリケーションのテスト

長期間にわたって値を公開する監視可能なシーケンスがある場合、リアルタイムでテストすることは一気にできます。 リアクティブ拡張ライブラリには、実際に時間が経過するのを待たずに、この種の時間依存コードのテストを支援する TestScheduler 型が用意されています。 TestScheduler は VirtualScheduler を継承し、エミュレートされた時間内にシーケンスを作成、発行、サブスクライブできます。 たとえば、適切なスケールを維持しながら、5 日かかるパブリケーションを 2 分の実行に圧縮できます。 また、過去に実際に発生したシーケンス (たとえば、前年の株価ティックのシーケンス) を取得し、リアルタイムで新しい値をプッシュしているかのように計算またはサブスクライブすることもできます。

ファクトリ メソッド Start は、キューが空になるまでスケジュールされたすべてのタスクを実行します。または、キューに登録されたタスクが指定された時刻にのみ実行されるように、時間を指定できます。

次の例では、OnNext 通知を指定して、ホット監視可能なシーケンスを作成します。 次に、テスト スケジューラを開始し、ホット監視可能シーケンスをサブスクライブして破棄するタイミングを指定します。 Start メソッドは、リスト内のすべての通知を記録する Messages プロパティを含む ITestableObserver のインスタンスを返します。

シーケンスが完了したら、ReactiveAssert.AreElementEqual メソッドを使用して Messages プロパティを比較し、期待される値の一覧と共に、両方が同じかどうかを確認します (項目数が同じで、項目が同じ順序で等しい)。 そうすることで、期待される通知を実際に受信したことを確認できます。 この例では、150 でのみサブスクライブを開始するため、値 abcを見落とします。 ただし、これまでに受け取った値を 400 で比較すると、シーケンスをサブスクライブした後、実際には公開されたすべての値が受信されていることがわかります。 また、通知が OnCompleted 適切なタイミングで 500 で発生したことを確認します。 さらに、サブスクリプション情報は、CreateHotObservable メソッドによって返される ITestableObservable 型によってもキャプチャされます。

同じ方法で、ReactiveAssert.AreElementsEqual を使用して、サブスクリプションが予想される時間に実際に発生したことを確認できます。

using System;
using System.Reactive;
using System.Reactive.Linq;
using Microsoft.Reactive.Testing;

class Program : ReactiveTest
{
    static void Main(string[] args)
    {
        var scheduler = new TestScheduler();

        var input = scheduler.CreateHotObservable(
            OnNext(100, "abc"),
            OnNext(200, "def"),
            OnNext(250, "ghi"),
            OnNext(300, "pqr"),
            OnNext(450, "xyz"),
            OnCompleted<string>(500)
            );

        var results = scheduler.Start(
            () => input.Buffer(() => input.Throttle(TimeSpan.FromTicks(100), scheduler))
                       .Select(b => string.Join(",", b)),
            created: 50,
            subscribed: 150,
            disposed: 600);

        ReactiveAssert.AreElementsEqual(results.Messages, new Recorded<Notification<string>>[] {
                OnNext(400, "def,ghi,pqr"),
                OnNext(500, "xyz"),
                OnCompleted<string>(500)
            });

        ReactiveAssert.AreElementsEqual(input.Subscriptions, new Subscription[] {
                Subscribe(150, 500),
                Subscribe(150, 400),
                Subscribe(400, 500)
            });
    }
}

Rx アプリケーションのデバッグ

Do 演算子を使用して、Rx アプリケーションをデバッグできます。 Do 演算子を使用すると、監視可能なシーケンスの各項目に対して実行するさまざまなアクションを指定できます (たとえば、アイテムの印刷やログ記録など)。 これは、多数の演算子を連結していて、各レベルで生成される値を知りたい場合に特に役立ちます。

次の例では、1 秒ごとに整数を生成する Buffer の例を再利用し、それぞれ 5 つの項目を保持できるバッファーに入れます。 LINQ 演算子を使用した監視可能シーケンスのクエリに関するトピックの元の例では、バッファーがいっぱい (空になるまで) に最終的な Observable(IList<>) シーケンスのみをサブスクライブします。 ただし、この例では、Do 演算子を使用して、元のシーケンス (1 秒ごとに整数) によって値がプッシュされるときに値を出力します。 バッファーがいっぱいになると、オブザーバーがサブスクライブする最後のシーケンスとしてこのすべてを渡す前に、Do 演算子を使用して状態を出力します。

var seq1 = Observable.Interval(TimeSpan.FromSeconds(1))
           .Do(x => Console.WriteLine(x.ToString()))
           .Buffer(5)
           .Do(x => Console.WriteLine("buffer is full"))
           .Subscribe(x => Console.WriteLine("Sum of the buffer is " + x.Sum()));
Console.ReadKey();

このサンプルからわかるように、サブスクリプションは、一連のチェーンされた監視可能なシーケンスの受信者側にあります。 最初に、Interval 演算子を使用して、2 番目の整数で区切られた監視可能なシーケンスを作成します。 次に、Buffer 演算子を使用して 5 つの項目をバッファーに格納し、バッファーがいっぱいの場合にのみ別のシーケンスとして送信します。 最後に、これは Subscribe 演算子に渡されます。 データは、オブザーバーにプッシュされるまで、これらすべての中間シーケンスを伝達します。 同様に、サブスクリプションは逆方向にソース シーケンスに反映されます。 このような伝達の途中に Do 演算子を挿入することで、.NET の Console.WriteLine や C の printf() を使用してデバッグを実行するのと同じように、このようなデータ フローを "スパイ" できます。

Timestamp 演算子を使用して、監視可能なシーケンスによって項目がプッシュされた時刻を確認することもできます。 これは、精度を確保するために時間ベースの操作のトラブルシューティングに役立ちます。 「 単純な監視可能シーケンスの作成とサブスクライブ 」トピックの次の例を思い出してください。このトピックでは、Timestamp 演算子をクエリに連結して、ソース シーケンスによってプッシュされた各値が発行時までに追加されるようにします。 これにより、このソース シーケンスをサブスクライブするときに、その値とタイムスタンプの両方を受け取ることができます。

Console.WriteLine(“Current Time: “ + DateTime.Now);

var source = Observable.Timer(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(1))
                       .Timestamp();
using (source.Subscribe(x => Console.WriteLine("{0}: {1}", x.Value, x.Timestamp)))
      {
           Console.WriteLine("Press any key to unsubscribe");
           Console.ReadKey();
      }
Console.WriteLine("Press any key to exit");
Console.ReadKey();

出力は次のようになります。

Current Time: 5/31/2011 5:35:08 PM

Press any key to unsubscribe

0: 5/31/2011 5:35:13 PM -07:00

1: 5/31/2011 5:35:14 PM -07:00

2: 5/31/2011 5:35:15 PM -07:00

Timestamp 演算子を使用して、最初の項目が実際にシーケンスの 5 秒後にプッシュされ、各項目が 1 秒後に発行されることを確認しました。

さらに、デバッグに役立つラムダ式内にブレークポイントを設定することもできます。 通常、クエリ全体にブレークポイントを設定する場合は、特定の値を歌わずにクエリ全体を検索できます。 この制限を回避するには、クエリの途中に Select 演算子を挿入してブレークポイントを設定し、Select ステートメントで return ステートメントを使用してソースと同じ値を出力します。 その後、ステートメント行にブレークポイントを return 設定し、クエリの途中で値を調べることができます。

var seq = Observable.Interval(TimeSpan.FromSeconds(1))
          .Do(x => Console.WriteLine(x.ToString()))
          .Buffer(5)
          .Select(y => { 
                  return y; }) // set a breakpoint at this line
          .Do(x => Console.WriteLine("buffer is full"))
          .Subscribe(x => Console.WriteLine("Sum of the buffer is " + x.Sum()));
Console.ReadKey();

この例では、ブレークポイントが行に return y 設定されています。 プログラムにデバッグすると、変数が y[ローカル] ウィンドウに表示され、その合計カウント (5)を調べることができます。 を展開 yすると、リスト内の各項目 (値や型を含む) を調べることもできます。

または、ラムダ式をステートメントラムダ式に変換し、ステートメントが独自の行になるようにコードを書式設定してから、ブレークポイントを設定することもできます。

デバッグが完了したら、Do 呼び出しと Select 呼び出しを削除できます。

参照

概念

単純な監視可能シーケンスの作成とサブスクライブ
LINQ 演算子を使用した監視可能シーケンスのクエリ
スケジューラの使用