【问题标题】:Unit tests failing with Observable.FromAsync and Observable.Switch单元测试因 Observable.FromAsync 和 Observable.Switch 而失败
【发布时间】:2015-08-13 09:01:07
【问题描述】:

我在测试使用Observable.FromAsync<T>() 和Observable.Switch<T>() 的类时遇到了麻烦。它所做的是等待一个可观察的触发器产生一个值,然后它开始一个异步操作,最后在一个输出序列中重新收集所有操作的结果。它的要点是这样的:

var outputStream = triggerStream
  .Select(_ => Observable
    .FromAsync(token => taskProducer.DoSomethingAsync(token)))
  .Switch();

我用最少的部分进行了一些健全性检查测试,以了解发生了什么,这是 cmets 中的测试结果:

class test_with_rx : nspec
{
  void Given_async_task_and_switch()
  {
    Subject<Unit> triggerStream = null;
    TaskCompletionSource<long> taskDriver = null;
    ITestableObserver<long> testObserver = null;
    IDisposable subscription = null;

    before = () =>
    {
      TestScheduler scheduler = new TestScheduler();
      testObserver = scheduler.CreateObserver<long>();
      triggerStream = new Subject<Unit>();
      taskDriver = new TaskCompletionSource<long>();

      // build stream under test
      IObservable<long> streamUnderTest = triggerStream
        .Select(_ => Observable
          .FromAsync(token => taskDriver.Task))
        .Switch();

      /* Also tried with this Switch() overload
      IObservable<long> streamUnderTest = triggerStream
          .Select(_ => taskDriver.Task)
          .Switch(); */

      subscription = streamUnderTest.Subscribe(testObserver);
    };

    context["Before trigger"] = () =>
    {
      it["Should not notify"] = () => testObserver.Messages.Count.Should().Be(0);
      // PASSED
    };

    context["After trigger"] = () =>
    {
      before = () => triggerStream.OnNext(Unit.Default);

      context["When task completes"] = () =>
      {
        long result = -1;

        before = () =>
        {
          taskDriver.SetResult(result);
          //taskDriver.Task.Wait();  // tried with this too
        };

        it["Should notify once"] = () => testObserver.Messages.Count.Should().Be(1);
        // FAILED: expected 1, actual 0

        it["Should notify task result"] = () => testObserver.Messages[0].Value.Value.Should().Be(result);
        // FAILED: of course, index out of bound
      };
    };

    after = () =>
    {
      taskDriver.TrySetCanceled();
      taskDriver.Task.Dispose();
      subscription.Dispose();
    };
  }
}

在我也用模拟完成的其他测试中,我可以看到传递给 FromAsync 的 Func 实际上被调用了(例如taskProducer.DoSomethingAsync(token)),但是看起来没有更多的后续,并且输出流不会产生价值。

我还尝试在达到预期之前插入一些 Task.Delay(x).Wait() 或一些 taskDriver.Task.Wait(),但没有成功。

我阅读了this SO thread 并且知道调度程序,但乍一看我以为我不需要它们,没有使用ObserveOn()。我错了吗?我错过了什么?助教

为了完整性,测试框架是 NSpec,断言库是 FluentAssertions。

【问题讨论】:

  • 阅读 Paul Betts 的答案(例如 here),可能在测试期间 FromAsync 或 Switch() 在后台使用 MSTest.exe 提供的相同空闲调度程序/调度程序 ...

标签: c# unit-testing system.reactive


【解决方案1】:

您遇到的是一起测试 Rx 和 TPL 的案例。 可以在 here 找到详尽的解释,但我会尝试为您的特定代码提供建议。

基本上您的代码运行良好,但您的测试却不行。 Observable.FromAsync 将在提供的任务上转换为 ContinueWith,这将在任务池上执行,因此是异步的。

修复您的测试的许多方法:(从丑陋到复杂)

  1. 结果集后休眠(注意等待不起作用,因为等待不等待继续)

    taskDriver.SetResult(result);
    Thread.Sleep(50);
    
  2. 在执行 FromAsync 之前设置结果(因为如果任务完成,FromAsync 将立即返回一个 IObservable,也就是将跳过 ContinueWith)

    taskDriver.SetResult(result);
    triggerStream.OnNext(Unit.Default);
    
  3. 用可测试的替代方法替换 FromAsync,例如

    public static IObservable<T> ToObservable<T>(Task<T> task, TaskScheduler scheduler)
    {
        if (task.IsCompleted)
        {
            return task.ToObservable();
        }
        else
        {
            AsyncSubject<T> asyncSubject = new AsyncSubject<T>();
            task.ContinueWith(t => task.ToObservable().Subscribe(asyncSubject), scheduler);
            return asyncSubject.AsObservable<T>();
        }
    }
    

(使用synchronous TaskScheduler 或testable one)

【讨论】:

  • 感谢您的回答。选项#2 的第一次尝试有效。我偶然在其他测试用例中使用了它,没想到它是如此至关重要。在过去不工作的其他情况下,我使用 Thread.Sleep(),但感觉有点糟糕。对于采用 IScheduler 输入参数的 FromAsync() 或 ToObservable() 在 Rx 中是否已经存在任何重载?
  • 例如类似this from Rx Githuib
  • 不错的发现,不知道它被添加了recently!我猜应该是 2.3.0 测试版。
  • 是的,有一个比较老的issue,在 James World 的this answer 中提到
猜你喜欢
  • 2013-07-06
  • 2014-06-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-12
  • 2014-04-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多