使用Reactive Extensions对事件进行单元测试

Mar*_*age 6 .net c# unit-testing system.reactive

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

public sealed class ClassUnderTest : IDisposable {

  Subject<Unit> subject = new Subject<Unit>();

  public IObservable<Unit> SomethingHappened {
    get { return this.subject.AsObservable(); }
  }

  public void DoSomething() {
    this.subject.OnNext(new Unit());
  }

  public void Dispose() {
    this.subject.OnCompleted();
  }

}
Run Code Online (Sandbox Code Playgroud)

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

这是我测试的方式:

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

// Act
classUnderTest.DoSomething();

// Assert
Assert.IsTrue(eventFired);
Run Code Online (Sandbox Code Playgroud)

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

Mar*_*age 13

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

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

测试类应该派生ReactiveTestMicrosoft.Reactive.Testing命名空间.该测试基于TestScheduler为测试提供虚拟时间的测试.

TestScheduler.Schedule方法可用于在虚拟时间内在某些点(滴答)排队活动.测试由执行TestScheduler.Start.这将返回一个ITestableObserver<T>可用于断言的例子,例如通过使用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);
  }

}
Run Code Online (Sandbox Code Playgroud)

TestScheduler.Schedule用于DoSomething在20时调度呼叫(以刻度表测量).

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

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

测试验证调用DoSomething立即触发可观察对象SomethingHappened.


Ser*_*hov 5

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

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

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

    [TestMethod]
    public void SimpleTest()
    {
        var sched = new TestScheduler();
        var subject = new Subject<Unit>();
        var observable = subject.AsObservable();

        var o = sched.CreateHotObservable(
            OnNext(210, new Unit())
            ,OnCompleted<Unit>(250)
            );
        var results = sched.Run(() =>
                                    {
                                        o.Subscribe(subject);
                                        return observable;
                                    });
        results.AssertEqual(
            OnNext(210, new Unit())
            ,OnCompleted<Unit>(250)
            );
    }:
Run Code Online (Sandbox Code Playgroud)

编辑:您也可以隐式调用.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()));
Run Code Online (Sandbox Code Playgroud)

我的观点是 - 在最简单的情况下,你需要确保事件被触发(你是在检查你的工作正常).但随着复杂性的提高,您开始测试时序,完成或其他需要虚拟调度程序的东西.但是,与"正常"测试相反,使用虚拟调度程序的测试的性质是立即测试整个observable,而不是"原子"操作.

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

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

  • 这种方法似乎非常适合测试Rx本身.但是,我不想合成`OnNext`调用.相反,我想断言我正在测试的类中对方法的调用实际上导致在`IObservable`上调用`OnNext`. (2认同)