使用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();