当使用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,则此测试将起作用。
这段代码的行为完全符合我的预期。
在测试中,您在测试调度程序上安排两个操作来引发事件。一个在时间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
在订阅中列出事件负载来简化检查,以实现更可读的相等性检查。