2010-10-12 2 views
6

Я использую 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-подобный синтаксис, чтобы сделать утверждения на IObservable, мы надеемся сделать тест более удобочитаемым.

+1

BTW, я думаю, что переменная отлично. Вышеприведенный код легко читается, что является самым важным. Ответ @ PL приятный и изящный, но вам нужно напрягаться, чтобы понять, что происходит ... Может быть, превратить его в расширение FailIfNothingHappened() –

+0

@Sergey Aldoukhov: Я согласен, но ответ PL понял, как я могу использовать ' Материализуйте ', чтобы рассуждать о том, как ведет себя мой' IObservable'. И для более сложных тестов с использованием переменных для захвата того, что произошло, может быть труднее понять. Кроме того, создание расширения, как вы предлагаете, вероятно, облегчит понимание того, что происходит. –

+0

Я отредактировал свой вопрос, чтобы сделать его более понятным, что я хочу. –

ответ

11

Этот ответ был обновлен до версии 1.0 версии Rx.

Официальная документация по-прежнему скудная, но 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); 
    } 

} 

TestScheduler.Schedule используется для планирования вызова DoSomething во время 20 (измеряется в клещах).

Затем TestScheduler.Start используется для проведения фактического испытания на наблюдаемом SomethingHappened. Срок действия подписки контролируется аргументами вызова (снова измеряется в тиках).

Наконец ReactiveAssert.AreElementsEqual используется для проверки того, что OnNext был вызван в 20 раз, как ожидалось.

Испытание подтверждает, что вызов DoSomething немедленно вызывает наблюдаемый SomethingHappened.

0

Не уверен, что более свободно, но это сделает трюк без введения переменной.

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(); 
+1

Я уверен, что это не сработает, Утверждение в подписке часто приводит к странным вещам, и тест все еще проходит. –

+0

Для проверки отказа вы должны прокомментировать строку с вызовом OnNext. –

+1

Просто точка; AsObservable() не служит цели в этом примере. –

3

Этот тип испытаний для наблюдаемых будет неполным. Совсем недавно команда RX опубликовала тестовый планировщик и некоторые расширения (которые BTW используют для тестирования библиотеки). Используя их, вы можете не только проверить, произошло ли что-то, но и убедиться в правильности даты и порядка времени и порядка. В качестве бонуса тестовый планировщик позволяет запускать ваши тесты в «виртуальном времени», поэтому тесты запускаются мгновенно, независимо от того, сколько больших задержек вы используете внутри.

Джеффри ван Гог из команды RX 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) 
      ); 
    }: 

EDIT: Вы также можете позвонить .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())); 

Моя точка - в простейших ситуациях, вам нужно только, чтобы убедиться, что событие вызывается (Fe, вы проверяете вашу Где работает C orrectly). Но тогда, когда вы продвигаетесь по сложности, вы начинаете тестирование времени, или завершения, или чего-то еще, что требует виртуального планировщика. Но характер теста с использованием виртуального планировщика, напротив «нормальных» тестов, - это проверка всего наблюдаемого сразу, а не «атомных» операций.

Возможно, вам придется переключиться на виртуальный планировщик где-то в дороге - почему бы не начать с него в начале?

P.S. Кроме того, вам придется прибегать к другой логике для каждого тестового примера - т. Е. у вас было бы совсем другое наблюдение за проверкой того, что что-то не случилось с тем, что произошло.

+2

Этот подход кажется очень подходящим для тестирования чего-то вроде Rx. Однако я не хочу синтезировать вызовы OnNext. Вместо этого я хочу утверждать, что вызов метода в тестируемом классе фактически приводит к вызову 'OnNext' на' IObservable'. –