代码之家  ›  专栏  ›  技术社区  ›  Martin Liversage

使用响应扩展对事件进行单元测试

  •  6
  • Martin Liversage  · 技术社区  · 14 年前

    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();
      }
    
    }
    

    显然我真正的课程更复杂。我的目标是验证对被测类执行某些操作会导致 IObservable IDisposable 打电话来 OnCompleted 当对象被释放时,在主题上进行测试要容易得多。

    以下是我的测试方法:

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

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

    3 回复  |  直到 13 年前
        1
  •  13
  •   Martin Liversage    12 年前

    这个答案已经更新到现在发布的Rx 1.0版。

    官方文件仍然很少,但是 Testing and Debugging Observable Sequences 在MSDN上是一个很好的起点。

    ReactiveTest Microsoft.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);
      }
    
    }
    

    用于安排呼叫 DoSomething 在时间20处(以刻度为单位)。

    那么 TestScheduler.启动 SomethingHappened

    最后 ReactiveAssert.AreElementsEqual 用于验证 OnNext 如预期的那样在20点被叫来。

    测试验证调用 剂量测定 立即发射可观察到的 .

        2
  •  5
  •   Sergey Aldoukhov    14 年前

    这种对可见光的测试是不完整的。就在最近,RX团队发布了测试调度器和一些扩展(顺便说一下,它们在内部用于测试库)。 使用这些,您不仅可以检查是否发生了什么,而且还可以确保 是正确的。另外,测试调度程序允许您在“虚拟时间”内运行测试,因此无论您在内部使用多大的延迟,测试都会立即运行。

    RX团队的Jeffrey van Gogh published an article on how to do such kind of testing.

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

        [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)
                );
        }:
    

    编辑:也可以隐式调用.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()));
    

    因此,您可能不得不切换到虚拟调度程序,在某个地方的道路上-为什么不从它开始呢?

        3
  •  0
  •   PL.    14 年前

    不确定是否更流畅,但这可以在不引入变量的情况下完成任务。

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