У меня есть класс, инкапсулирующий 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;
}
}
Как я могу проверить его правильно? Все мои попытки либо преждевременно выходят, либо вызывают взаимоблокировки.
Ключ к тестированию модулей 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 */);
Не знаю, работает ли это для вашего сценария, но это может спровоцировать некоторые идеи!
IScheduler
реализованный в вашем модульном тесте, какTestScheduler
.