2013-07-25 56 views
1

測試觀測量時如何使用Task.Run虛擬時間,我有以下功能我想測試與無擴展

/// Items are processed asynchronously via fn as they arrive. However 
/// if an item arrives before the last asynchronous operation has 
/// completed then the cancellation token passed to fn will be 
/// triggered enabling the task to be canceled in a best effort 
/// way. 
public static IObservable<U> SelectWithCancellation<T, U> 
    (this IObservable<T> This 
    , Func<CancellationToken, T, Task<U>> fn 
    ) 
{ 
    return This 
     .Select(v=>Observable.FromAsync(token=>fn(token, v))) 
     .Switch(); 
} 

我要測試它,最好的我已經能夠拿出 工作原理如下。首先,我創建一個長時間運行的任務, 可以取消

public Task<string> JobTask 
    (CancellationToken token 
    , string input 
    ) 
{ 
    return Task.Factory.StartNew(() => 
     { 
      if (input == "C" || input == "E") 
      { 
       while (!token.IsCancellationRequested) ; 
      } 
      return input; 
     } 
    ); 
} 

,然後我測試,它可以真正起作用

public class SelectWithCancelationSpec : ReactiveTest 
{ 
    TestScheduler _Scheduler = new TestScheduler(); 

    [Fact] 
    public void ShouldWork() 
    { 
     var o = _Scheduler.CreateHotObservable 
      (OnNext(100, "A") 
      , OnNext(200, "B") 
      , OnNext(300, "C") 
      , OnNext(400, "D") 
      , OnNext(500, "E") 
      , OnNext(500, "F") 
      ); 

     List<string> actual = new List<string>(); 

     o 
      .SelectWithCancellation(JobTask) 
      .Subscribe(v => actual.Add(v)); 

     var delay = 100; 
     _Scheduler.AdvanceTo(150); 
     Thread.Sleep(delay); 
     _Scheduler.AdvanceTo(250); 
     Thread.Sleep(delay); 
     _Scheduler.AdvanceTo(350); 
     Thread.Sleep(delay); 
     _Scheduler.AdvanceTo(450); 
     Thread.Sleep(delay); 
     _Scheduler.AdvanceTo(550); 
     Thread.Sleep(delay); 
     _Scheduler.AdvanceTo(650); 


     var expected = new[] { "A", "B", "D", "F" }; 

     actual 
      .ShouldBeEquivalentTo(expected); 

    } 
} 

的問題是,我不得不引進real time到 測試。這是因爲我的模擬JobTask運行在線程池的真實 線程上,並且不考慮測試調度程序的虛擬時間 。如果我不把延遲 置於AdvanceTo調用之間,會發生什麼情況是因爲JobTask需要太長的時間來處理,所以我在測試中放棄了多於 的消息。

問題是。我如何創建一個尊重 虛擬時間的JobTask,並允許我測試是否可以成功刪除 預期的消息。

回答

1

關鍵是創建一個TestScheduler知道的tick事件流。 爲此目的我創建的擴展方法

public static class TestSchedulerExtensions 
{ 
    public static IObservable<Unit> CreateTickObserver(this TestScheduler s, int startTick, int endTick, int tickStep) 
    { 
     var ticks = Enumerable.Repeat(1, Int32.MaxValue) 
      .Select((v, i) => i * tickStep + startTick) 
      .TakeWhile(v => v <= endTick) 
      .Select(tick => ReactiveTest.OnNext(tick, Unit.Default)); 

     return s.CreateColdObservable(ticks.ToArray()); 

    } 
} 

然後另一個擴展方法,以協助在測試條件下創建任務

public static Func<CancellationToken,U,Task<T>> 
     AsyncSelectorFactory<T, U> 
     (this TestScheduler s 
     , int duration 
     , int interval 
     , Func<CancellationToken, U, IObservable<Unit>, Task<T>> fn 
     ) 
    { 
     var ticker = s.CreateTickObserver(0, duration, interval); 
     return (c, u) => 
     { 
      return fn(c, u, ticker); 
     }; 
    } 

的TaskFactory產生能夠產生的任務,但它 傳遞一個功能在測試調度程序的控制下的股票。那個 股票可用於導致延遲或其他事情。

注意上面我們正在等待_Ticker sourced observable在任務中創建延遲 。而現在我們的測試案例看起來像

現在的測試僅僅是

public class SelectWithCancelationSpec : ReactiveTest 
{ 
    TestScheduler _Scheduler = new TestScheduler(); 

    [Fact] 
    public void ShouldWork() 
    { 

     var o = _Scheduler.CreateColdObservable 
      (OnNext(100, "A") 
      , OnNext(200, "B") 
      , OnNext(300, "C") 
      , OnNext(400, "D") 
      , OnNext(500, "E") 
      , OnNext(600, "F") 
      ); 

     int cancelCount = 0; 
     var job = _Scheduler.AsyncSelectorFactory<string,string> 
      (1000 
      , 10 
      , async (token, input, ticker) => { 
       if (input == "C" || input == "E") 
       { 
        await ticker.TakeWhile(v => !token.IsCancellationRequested); 
        cancelCount++; 
       } 
       return input; 
      }); 


     var actual = _Scheduler.Start(() => 
     { 
      return o.SelectWithCancellation(job); 
     } 
     , created: 0 
     , subscribed: 1 
     , disposed: 1000 
     ); 

     var expected = new[] { "A", "B", "D", "F" }; 

     cancelCount.Should().Be(2); 


     actual.Messages.Select(v=>v.Value.Value) 
      .ShouldBeEquivalentTo(expected); 

    } 



}