使用Reactive Extensions对事件进行unit testing

我正在使用Reactive Extensions for .NET(Rx)将事件公开为IObservable 。 我想创建一个unit testing,我断言特定事件被触发。 这是我要测试的类的简化版本:

 public sealed class ClassUnderTest : IDisposable { Subject subject = new Subject(); public IObservable SomethingHappened { get { return this.subject.AsObservable(); } } public void DoSomething() { this.subject.OnNext(new Unit()); } public void Dispose() { this.subject.OnCompleted(); } } 

显然我真正的课程更复杂。 我的目标是validation对被测试类执行某些操作会导致在IObservable上发出一系列事件。 幸运的是,我想测试的类实现IDisposable并在处理对象时对主题调用OnCompleted使得测试更容易。

这是我测试的方式:

 // Arrange var classUnderTest = new ClassUnderTest(); var eventFired = false; classUnderTest.SomethingHappened.Subscribe(_ => eventFired = true); // Act classUnderTest.DoSomething(); // Assert Assert.IsTrue(eventFired); 

使用变量来确定事件是否被触发也不是太糟糕,但在更复杂的场景中,我可能想validation是否触发了特定的事件序列。 这可能不是简单地在变量中记录事件然后对变量做断言吗? 能够使用流畅的类LINQ语法在IObservable上进行断言有望使测试更具可读性。

此答案已更新至现已发布的Rx版本1.0。

官方文档仍然很少,但MSDN上的测试和调试可观察序列是一个很好的起点。

测试类应该从Microsoft.Reactive.Testing命名空间中的ReactiveTest派生。 该测试基于TestScheduler ,为测试提供虚拟时间。

TestScheduler.Schedule方法可用于在虚拟时间内在某些点(滴答)排队活动。 测试由TestScheduler.Start执行。 这将返回一个ITestableObserver ,可以通过使用ReactiveAssert类来用于断言。

 public class Fixture : ReactiveTest { public void SomethingHappenedTest() { // Arrange var scheduler = new TestScheduler(); var classUnderTest = new ClassUnderTest(); // Act scheduler.Schedule(TimeSpan.FromTicks(20), () => classUnderTest.DoSomething()); var actual = scheduler.Start( () => classUnderTest.SomethingHappened, created: 0, subscribed: 10, disposed: 100 ); // Assert var expected = new[] { OnNext(20, new Unit()) }; ReactiveAssert.AreElementsEqual(expected, actual.Messages); } } 

TestScheduler.Schedule用于在时间20安排对DoSomething的调用(以滴答为单位)。

然后TestScheduler.Start用于对可观察的SomethingHappened执行实际测试。 订阅的生命周期由调用的参数控制(再次以刻度forms测量)。

最后, ReactiveAssert.AreElementsEqual用于validation在时间20按预期调用OnNext

测试validation调用DoSomething立即触发可观察的SomethingHappened

对可观测量的这种测试将是不完整的。 就在最近,RX团队发布了测试调度程序和一些扩展(他们在内部使用BTW来测试库)。 使用这些,您不仅可以检查是否发生了事情,还可以确保时间和顺序是正确的。 作为奖励,测试计划程序允许您在“虚拟时间”运行测试,因此无论您在内部使用多大延迟,测试都会立即运行。

来自RX团队的Jeffrey van Gogh 发表了一篇关于如何进行此类测试的文章。

使用上述方法进行的上述测试如下所示:

  [TestMethod] public void SimpleTest() { var sched = new TestScheduler(); var subject = new Subject(); var observable = subject.AsObservable(); var o = sched.CreateHotObservable( OnNext(210, new Unit()) ,OnCompleted(250) ); var results = sched.Run(() => { o.Subscribe(subject); return observable; }); results.AssertEqual( OnNext(210, new Unit()) ,OnCompleted(250) ); }: 

编辑:您也可以隐式调用.OnNext(或其他一些方法):

  var o = sched.CreateHotObservable(OnNext(210, new Unit())); var results = sched.Run(() => { o.Subscribe(_ => subject.OnNext(new Unit())); return observable; }); results.AssertEqual(OnNext(210, new Unit())); 

我的观点是 – 在最简单的情况下,你只需要确保事件被触发(你是在检查你的位置是否正常工作)。 但随着您在复杂性方面的进步,您开始测试时间,完成或其他需要虚拟调度程序的东西。 但是,与“正常”测试相反,使用虚拟调度程序的测试的性质是立即测试整个observable,而不是“primefaces”操作。

所以你可能不得不在未来的某个地方切换到虚拟调度程序 – 为什么不从头开始呢?

PS此外,你必须为每个测试用例采用不同的逻辑 – 你会有非常不同的可观察性来测试某些事情与发生的事情相反。

不确定更流畅,但这将在不引入变量的情况下完成。

 var subject = new Subject(); subject .AsObservable() .Materialize() .Take(1) .Where(n => n.Kind == NotificationKind.OnCompleted) .Subscribe(_ => Assert.Fail()); subject.OnNext(new Unit()); subject.OnCompleted();