C# 使用Observable.FromAsync和Observable.Switch进行单元测试失败

C# 使用Observable.FromAsync和Observable.Switch进行单元测试失败,c#,unit-testing,system.reactive,C#,Unit Testing,System.reactive,我在测试一个使用Observable.fromsync()和Observable.Switch()的类时遇到问题。它所做的是等待一个触发器产生一个值,然后启动一个异步操作,最后在一个输出序列中重新收集所有操作的结果。其要点如下: var outputStream=triggerStream .选择(=>可观察 .FromAsync(令牌=>taskProducer.DoSomethingAsync(令牌))) .开关(); 我用最基本的部分进行了一些健全性检查测试,以了解发生了什么,下面是带有

我在测试一个使用
Observable.fromsync()
Observable.Switch()的类时遇到问题。它所做的是等待一个触发器产生一个值,然后启动一个异步操作,最后在一个输出序列中重新收集所有操作的结果。其要点如下:

var outputStream=triggerStream
.选择(=>可观察
.FromAsync(令牌=>taskProducer.DoSomethingAsync(令牌)))
.开关();
我用最基本的部分进行了一些健全性检查测试,以了解发生了什么,下面是带有注释结果的测试:

class test_与_rx:nspec
{
无效给定的\u异步\u任务\u和\u开关()
{
主题triggerStream=null;
TaskCompletionSource taskDriver=null;
ITestableObserver testObserver=null;
IDisposable订阅=null;
之前=()=>
{
TestScheduler scheduler=新的TestScheduler();
testObserver=scheduler.CreateObserver();
triggerStream=新主题();
taskDriver=新的TaskCompletionSource();
//构建测试中的流
IObservable streamUnderTest=触发流
.选择(=>可观察
.FromAsync(令牌=>taskDriver.Task))
.开关();
/*还尝试了此开关()重载
IObservable streamUnderTest=触发流
.Select(=>taskDriver.Task)
.开关()*/
订阅=streamUnderTest.Subscribe(testObserver);
};
上下文[“在触发器之前”]=()=>
{
它[“不应通知”]=()=>testObserver.Messages.Count.Should().Be(0);
//通过
};
上下文[“触发器后”]=()=>
{
before=()=>triggerStream.OnNext(单位默认值);
上下文[“任务完成时”]=()=>
{
长期结果=-1;
之前=()=>
{
taskDriver.SetResult(结果);
//taskDriver.Task.Wait();//也尝试过这个
};
它[“应该通知一次”]=()=>testObserver.Messages.Count.Should().Be(1);
//失败:应为1,实际为0
它[“应该通知任务结果”]=()=>testObserver.Messages[0].Value.Value.Should().Be(结果);
//失败:当然,索引超出范围
};
};
之后=()=>
{
taskDriver.TrySetCanceled();
taskDriver.Task.Dispose();
subscription.Dispose();
};
}
}
在我用mock做的其他测试中,我可以看到传递给fromsync的Func实际上被调用了(例如,
taskProducer.DoSomethingAsync(token)
),但是接下来看起来没有什么了,输出流也不会产生值

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

我读过,也知道有调度程序,但乍看之下我认为我不需要它们,没有使用
ObserveOn()
。我错了吗?我错过了什么?助教


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

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

基本上,您的代码工作正常,但您的测试不正常。
可观察。FromAsync
将转换为所提供任务上的
ContinueWith
,该任务将在任务池上执行,因此是异步的

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

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

  • 在执行
    fromsync
    之前设置结果(因为如果任务完成,fromsync将立即返回IObservable,aka将跳过
    ContinueWith

  • 用可测试的替代方案替换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>();
        }
    }
    
    public static IObservable to observable(任务任务,任务调度程序)
    {
    如果(任务已完成)
    {
    return task.ToObservable();
    }
    其他的
    {
    AsyncSubject AsyncSubject=新建AsyncSubject();
    task.ContinueWith(t=>task.ToObservable().Subscribe(asyncSubject),调度器);
    返回asyncSubject.AsObservable();
    }
    }
    

  • (使用TaskScheduler或TaskScheduler)

    阅读Paul Betts的答案(例如),可能在测试期间从Async
    开关()
    在引擎盖下使用MSTest.exe提供的相同空闲调度器/调度器…感谢您的回答。第一次尝试使用选项2是可行的。我在其他测试用例中偶然使用了它,但我并不认为它如此重要。在过去的其他情况下,事情不起作用,我使用了Thread.Sleep(),但感觉有点糟糕。Rx中是否已经有任何重载可用于FromAsync()或ToObservable()获取ISScheduler输入参数?例如,类似于尼斯发现的内容,但不知道它已添加!我想应该是在2.3.0 betas中。是的,这是一个相对古老的概念,James World在中提到过
    taskDriver.SetResult(result);
    triggerStream.OnNext(Unit.Default);
    
    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>();
        }
    }