当使用TestScheduler将事件激发到具有ObserveOn的Observable.FromEventPatter

本文关键字:ObserveOn FromEventPatter Observable TestScheduler 事件 | 更新日期: 2023-09-27 18:26:53

我似乎在使用带有ObserveOn的Observable.FromEventPattern的TestScheduler的特定设置时遇到了问题。似乎正在发生的是,这两个事件都被触发了,但我只在第二个事件触发时观察第一个事件。当然,我本可以在这里做一些完全愚蠢的事情,我只是看不出我做错了什么。此外,我可能完全错过了一点或技巧:-)

有人能向我解释一下,如果我领先2分,我只看到1个事件,我在知识上遗漏了什么,或者我做错了什么吗?

我已经阅读了我关于Lee Campbells的大部分信息http://www.introtorx.com/(我发现了一个极好的知识源泉:-)

我正在使用:

Moq V4.2.1409.1722

Rx V 2.2.5.0

XUnit V 1.9.21705

这是我的密码。只是我自己的事件参数,这样我就可以观察到正在观察的数据。

public class MyEventArgs : EventArgs
{
    public int Data { get; private set; }
    public MyEventArgs(int data)
    {
        Data = data;
    }
}

一个带有将被模拟的EventHandler的接口。

public interface ITmp
{
    event EventHandler<MyEventArgs> tmpEvent;
}

这个类具有观察器并监视传入的tmp对象中的事件,它还需要一个调度器,这样我就可以测试这个观察器。

internal class SomeClass2
{
    private IObservable<EventPattern<MyEventArgs>> _observable;
    public SomeClass2(ITmp tmp, IScheduler scheduler)
    {
        _observable = Observable.FromEventPattern<MyEventArgs>(h => tmp.tmpEvent += h, h => tmp.tmpEvent -= h)
                                .Do(next => Console.WriteLine("Item came in...{0}", next.EventArgs.Data))
                                .ObserveOn(scheduler);
    }
    public IObservable<EventPattern<MyEventArgs>> Raw()
    {
        return _observable;
    }
}

测试。

public class Tests
{
    [Fact]
    public void FactMethodName2()
    {
        var mockedTmp = new Mock<ITmp>();
        var testScheduler = new TestScheduler();
        var temp = new SomeClass2(mockedTmp.Object, testScheduler);
        var count = 0;
        var myEventArgsObserved = new List<MyEventArgs>();
        temp.Raw().Subscribe(
            next =>
            {
                count++;
                myEventArgsObserved.Add(next.EventArgs);
            });
        testScheduler.Schedule(TimeSpan.FromTicks(1), () => mockedTmp.Raise(tmp => tmp.tmpEvent += null, new MyEventArgs(1)));
        testScheduler.Schedule(TimeSpan.FromTicks(2), () => mockedTmp.Raise(tmp => tmp.tmpEvent += null, new MyEventArgs(2)));
        testScheduler.AdvanceBy(1);
        testScheduler.AdvanceBy(1);
        Assert.Equal(2, count);
    }
}

控制台输出:

项目进来了。。。1

项目进来了。。。2

断言失败,因为此时它只观察到1个事件。我已经经历了这一过程,并观察到第一个事件直到第二个事件启动才被观察到。

顺便说一句,如果我添加另一个AdvanceBy(1)或使用testScheduler.Start而不是AdvanceBy,或者如果我删除ObserveOn并将调度器传递到FromEventPattern,则此测试将起作用。

当使用TestScheduler将事件激发到具有ObserveOn的Observable.FromEventPatter

这段代码的行为完全符合我的预期。

在测试中,您在测试调度程序上安排两个操作来引发事件。一个在时间T=1,一个在时刻T=2。

然后将测试调度程序提前到时间T=1。在这一点上,测试调度程序检查其计划的操作,以查看需要运行什么。它将选择引发事件的第一个操作。

此事件将由事件的可观察订阅者触发和拾取,这是由于ObserveOn运算符引起的订阅。然后,将调度一个调用,以尽快将OnNext提升到测试调度程序上的订户。测试调度程序将不会执行此调度操作,直到时间再次提前

这绝对是设计出来的。其目的是能够控制和观察操作的级联,并模拟Rx事件的调度和执行需要一定时间的现实。你真的不会想要其他方式。

因此,在下一个时间片T=2上,首先引发第二个事件(它是首先调度的),然后在ObserveOn的订户上激发针对第一个事件的OnNext调用。等等。

这样想吧——通过调度器的每个操作至少要花费一个时间单位。

要看到这一点,如果删除ObserveOn行,则删除中间调度,编写的测试将通过

在编写反应性测试时,通常需要考虑这些影响并调整断言。出于这个原因,作为一种最佳实践,我建议将行动安排在至少1000个刻度之间。我倾向于用TimeSpan.FromSeconds(x).Ticks这样的表达式来获得tick,并且经常有像TimeSpan.FromSeconds(x).Ticks + epsilon这样的断言,其中epsilon是一些预期的小常数。

我还使用了helper方法来断言从某个指定点开始的时间在某个预期的小范围内——这有助于提高测试的可读性,并避免在进行细微更改时必须调整所有内容。

综上所述,编写测试的一种更惯用的方法如下:

public class Tests : ReactiveTest
{
    [Fact]
    public void FactMethodName2()
    {
        var mockedTmp = new Mock<ITmp>();
        var testScheduler = new TestScheduler();
        var temp = new SomeClass2(mockedTmp.Object, testScheduler);
        const int c = 1;
        var eventArgs1 = new MyEventArgs(1);
        var eventArgs2 = new MyEventArgs(2);
        var results = testScheduler.CreateObserver<MyEventArgs>();
        temp.Raw().Select(ep => ep.EventArgs).Subscribe(results);
        testScheduler.Schedule(TimeSpan.FromTicks(1000),
            () => mockedTmp.Raise(tmp => tmp.tmpEvent += null, eventArgs1));
        testScheduler.Schedule(TimeSpan.FromTicks(2000),
            () => mockedTmp.Raise(tmp => tmp.tmpEvent += null, eventArgs2));
        testScheduler.Start();
        results.Messages.AssertEqual(
            OnNext(1000 + c, eventArgs1),
            OnNext(2000 + c, eventArgs2));
    }
}

在这里,我们从ReactiveTest派生测试类,它作为Assert中使用的OnNext辅助方法提供。使用Start而不是AdvanceBy运行测试调度程序,直到调度程序队列为空。使用测试调度程序创建的观测器(results),我们可以很容易地记录事件以及它们何时发生,并很容易地断言。

请注意,对于更复杂的测试,您可以使用谓词来测试记录的事件,而不是事件负载——例如,您可以执行类似args => args.Data == 1的操作,而不是在断言中使用eventArgs1。在这里,我通过Select在订阅中列出事件负载来简化检查,以实现更可读的相等性检查。