Как выполнить модульный тест Observable.Sample ()?

1

У меня есть класс, инкапсулирующий Observable.Sample() например:

class IntervalRequestScheduler
{
    private Subject<Action> _requests = new Subject<Action>();
    private IDisposable _observable;

    public IntervalRequestScheduler(TimeSpan requestLimit)
    {
        _observable = _requests.Sample(requestLimit)
                               .Subscribe(action => action());
    }

    public Task<T> ScheduleRequest<T>(Func<Task<T>> request)
    {
        var tcs = new TaskCompletionSource<T>();
        _requests.OnNext(async () =>
        {
            try
            {
                T result = await request();
                tcs.SetResult(result);
            }
            catch (Exception ex)
            {
                tcs.SetException(ex);
            }
        });
        return tcs.Task;
    }
}

Как я могу проверить его правильно? Все мои попытки либо преждевременно выходят, либо вызывают взаимоблокировки.

  • 0
    IScheduler реализованный в вашем модульном тесте, как TestScheduler .
  • 0
    @Stephen: да, это был мой первоначальный план, но потом я подумал, что возможно проверить Sample (), а не IScheduler. Но ваши комментарии заставляют меня думать, что это на самом деле неправильно. Тестировать легко, тестировать правильные вещи сложно :)
Теги:
unit-testing
system.reactive

1 ответ

3
Лучший ответ

Контроль времени в Rx

Ключ к тестированию модулей Rx понимает, как контролировать время с помощью TestScheduler. Все временные операторы в Rx-библиотеках берут необязательный параметр IScheduler, чтобы вы могли это сделать. Ваши операторы, работающие по времени, должны это сделать.

Итак, первое, что нам нужно сделать, это изменить конструктор IntervalRequestScheduler чтобы облегчить это:

public IntervalRequestScheduler(TimeSpan requestLimit,
                                // The scheduler is optional
                                IScheduler scheduler = null)
{
    // assign a default if necessary
    scheduler = scheduler ?? Scheduler.Default;

    // make sure to pass the scheduler in to 'Sample'
    _observable = _requests.Sample(requestLimit, scheduler)
                            .Subscribe(action => action());
}

Благодаря этому изменению мы можем контролировать время!

Здесь примерный единичный тест, который будет вызывать метод ScheduleRequest экземпляра IntervalRequestScheduler десять раз - затем увеличить время на выборку в течение одной секунды и проверить, что завершена только одна задача:

[Test]
public void ASingleTaskIsCompletedWhenTenAreScheduledWithinInterval()
{
    var scheduler = new TestScheduler();
    var sampleDuration = TimeSpan.FromSeconds(1);

    var intervalRequestScheduler = new IntervalRequestScheduler(sampleDuration,
                                                                scheduler);

    // use a helper method to create "requests"
    var taskFactories = Enumerable.Range(0, 10).Select(CreateRequest);

    // schedule the requests and collect the tasks into an array
    var tasks =
        (from tf in taskFactories
            select intervalRequestScheduler.ScheduleRequest(tf)).ToArray();

    // prove no tasks have completed
    var completedTasksCount = tasks.Count(t => t.IsCompleted);
    Assert.AreEqual(0, completedTasksCount);

    // this is the key - we advance time simulating a sampling period.
    scheduler.AdvanceBy(sampleDuration.Ticks);

    // now we see exactly one task has completed
    completedTasksCount = tasks.Count(t => t.IsCompleted);
    Assert.AreEqual(1, completedTasksCount);
}

// helper to create requests
public Func<Task<int>> CreateRequest(int result)
{
    return () => Task.Run(() => result);
}

В стороне

Я до сих пор просто сосредоточился на вопросе под рукой, но я хотел добавить, что фактическая мотивация для IntervalRequestScheduler немного неясна, и код выглядит немного грязным. Есть, возможно, лучшие способы достижения этого без смешивания завернутых задач и IObservables. Пребывание в мире Rx также облегчает прогнозирование прогнозов путем управления планировщиками. В приведенном выше коде есть какая-то гадость, которую я замалчивал, потому что вызов задачи асинхронен, и возможно, что одна запущенная задача может не завершиться к тому времени, когда вы ее протестируете, поэтому, чтобы быть абсолютно правильной, вам нужно попасть в грязный бизнес по мониторингу задач и предоставление им времени для начала и завершения. Но, надеюсь, вы увидите, что TestScheduler избегает всего этого беспорядка на стороне Rx.

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

Например, скажите, что вы runRequest функцию запроса типа Func<int,int> называемую runRequest и входной поток IObservable<int> requests обеспечивающий ввод каждого запроса (может быть, например, Subject<int>). Тогда вы могли бы просто:

requests.Sample(TimeSpan.FromSeconds(1), scheduler)
        .Select(input => request(input))
        .Subscribe(result => /* DoSomethingWithResult */);

Не знаю, работает ли это для вашего сценария, но это может спровоцировать некоторые идеи!

  • 0
    Спасибо за ваш код, он работает очень хорошо для меня! Я не знаю много о Rx, о тестировании я знаю еще меньше.
  • 0
    Вот некоторые рассуждения / описание того, для чего предназначен этот код: клиент API с HTTP-клиентом глубоко в его иерархии зависимостей выполняет вызов удаленного API, регулирующего поступающие запросы, например, не более 1 в секунду. Первоначально код просто делал запрос и получал ошибку 500, если было сделано слишком рано. Я хотел прозрачно добавить синхронизацию к остальной части кода. Так что http-клиент звонит, не зная о Rx и так далее.
Показать ещё 12 комментариев

Ещё вопросы

Сообщество Overcoder
Наверх
Меню