C# 单元测试一种带延时定时器的无功扩展方法

C# 单元测试一种带延时定时器的无功扩展方法,c#,unit-testing,system.reactive,reactiveui,reactivex,C#,Unit Testing,System.reactive,Reactiveui,Reactivex,我有下面的扩展方法 public static IObservable<T> RetryWithCount<T>(this IObservable<T> source, int retryCount, int delayMillisecondsToRetry, IScheduler executeScheduler = null, IScheduler retryScheduler = null)

我有下面的扩展方法

public static IObservable<T> RetryWithCount<T>(this IObservable<T> source, 
            int retryCount, int delayMillisecondsToRetry, IScheduler executeScheduler = null,
            IScheduler retryScheduler = null)
        {
            var retryAgain = retryCount + 1;
            return source
                .RetryX(
                    (retry, exception) =>
                        retry == retryAgain
                            ? Observable.Throw<bool>(exception)
                            : Observable.Timer(TimeSpan.FromMilliseconds(delayMillisecondsToRetry))
                                .Select(_ => true));
        }
        /// <summary>
        /// Retry the source using a separate Observable to determine whether to retry again or not.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source"></param>
        /// <param name="retryObservable">The observable factory used to determine whether to retry again or not. Number of retries & exception provided as parameters</param>
        /// <param name="executeScheduler">The scheduler to be used to observe the source on. If non specified MainThreadScheduler used</param>
        /// <param name="retryScheduler">The scheduler to use for the retry to be observed on. If non specified MainThreadScheduler used.</param>
        /// <returns></returns>
        public static IObservable<T> RetryX<T>(this IObservable<T> source,
            Func<int, Exception, IObservable<bool>> retryObservable, IScheduler executeScheduler = null,
            IScheduler retryScheduler = null)
        {
            if (retryObservable == null)
            {
                throw new ArgumentNullException(nameof(retryObservable));
            }

            if (executeScheduler == null)
            {
                executeScheduler = MainScheduler;
            }

            if (retryScheduler == null)
            {
                retryScheduler = MainScheduler;
            }

            // so, we need to subscribe to the sequence, if we get an error, then we do that again...
            return Observable.Create<T>(o =>
            {
                // whilst we are supposed to be running, we need to execute this
                var trySubject = new Subject<Exception>();

                // record number of times we retry
                var retryCount = 0;

                return trySubject.
                    AsObservable().
                    ObserveOn(retryScheduler).
                    SelectMany(e => Observable.Defer(() => retryObservable(retryCount, e))). // select the retry logic
                    StartWith(true). // prime the pumps to ensure at least one execution
                    TakeWhile(shouldTry => shouldTry). // whilst we should try again
                    ObserveOn(executeScheduler).
                    Select(g => Observable.Defer(source.Materialize)). // get the result of the selector
                    Switch(). // always take the last one
                    Do((v) =>
                    {
                        switch (v.Kind)
                        {
                            case NotificationKind.OnNext:
                                o.OnNext(v.Value);
                                break;

                            case NotificationKind.OnError:
                                ++retryCount;
                                trySubject.OnNext(v.Exception);
                                break;

                            case NotificationKind.OnCompleted:
                                trySubject.OnCompleted();
                                break;
                        }
                    }
                    ).Subscribe(_ => { }, o.OnError, o.OnCompleted);
            });
        }
最小辅助方法的优势

public static void AdvanceMinimal(this TestScheduler @this) => @this.AdvanceBy(TimeSpan.FromMilliseconds(1));
下面是RetryX扩展方法的成功单元测试

        [Test]
        public void should_retry_once()
        {
            // Arrange
            var tries = 0;
            var scheduler = new TestScheduler();
            var source = Observable
                .Defer(
                    () =>
                    {
                        ++tries;
                        return Observable.Throw<Unit>(new Exception());
                    });
            var retryAgain = 2;

            // Act
            source.RetryX(
                (retry, exception) =>
                {
                    var a = retry == retryAgain
                        ? Observable.Return(false)
                        : Observable.Return(true);

                    return a;
                }, scheduler, scheduler)
                .Subscribe(
                    _ => { },
                    ex => { });
            scheduler.AdvanceMinimal();

            // Assert
            Assert.IsTrue(tries == retryAgain);
        }
[测试]
public void应该重试一次()
{
//安排
var=0;
var scheduler=newtestscheduler();
var来源=可观测
.推迟(
() =>
{
++尝试;
返回Observable.Throw(newexception());
});
var-retryreach=2;
//表演
source.RetryX(
(重试,异常)=>
{
变量a=重试==再次重试
?可观察返回(假)
:可观察。返回(真);
返回a;
},调度程序,调度程序)
.订阅(
_ => { },
ex=>{});
scheduler.AdvanceMinimal();
//断言
Assert.IsTrue(trys==retryreach);
}
为了清晰起见,下面是RetryX扩展方法

public static IObservable<T> RetryWithCount<T>(this IObservable<T> source, 
            int retryCount, int delayMillisecondsToRetry, IScheduler executeScheduler = null,
            IScheduler retryScheduler = null)
        {
            var retryAgain = retryCount + 1;
            return source
                .RetryX(
                    (retry, exception) =>
                        retry == retryAgain
                            ? Observable.Throw<bool>(exception)
                            : Observable.Timer(TimeSpan.FromMilliseconds(delayMillisecondsToRetry))
                                .Select(_ => true));
        }
        /// <summary>
        /// Retry the source using a separate Observable to determine whether to retry again or not.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source"></param>
        /// <param name="retryObservable">The observable factory used to determine whether to retry again or not. Number of retries & exception provided as parameters</param>
        /// <param name="executeScheduler">The scheduler to be used to observe the source on. If non specified MainThreadScheduler used</param>
        /// <param name="retryScheduler">The scheduler to use for the retry to be observed on. If non specified MainThreadScheduler used.</param>
        /// <returns></returns>
        public static IObservable<T> RetryX<T>(this IObservable<T> source,
            Func<int, Exception, IObservable<bool>> retryObservable, IScheduler executeScheduler = null,
            IScheduler retryScheduler = null)
        {
            if (retryObservable == null)
            {
                throw new ArgumentNullException(nameof(retryObservable));
            }

            if (executeScheduler == null)
            {
                executeScheduler = MainScheduler;
            }

            if (retryScheduler == null)
            {
                retryScheduler = MainScheduler;
            }

            // so, we need to subscribe to the sequence, if we get an error, then we do that again...
            return Observable.Create<T>(o =>
            {
                // whilst we are supposed to be running, we need to execute this
                var trySubject = new Subject<Exception>();

                // record number of times we retry
                var retryCount = 0;

                return trySubject.
                    AsObservable().
                    ObserveOn(retryScheduler).
                    SelectMany(e => Observable.Defer(() => retryObservable(retryCount, e))). // select the retry logic
                    StartWith(true). // prime the pumps to ensure at least one execution
                    TakeWhile(shouldTry => shouldTry). // whilst we should try again
                    ObserveOn(executeScheduler).
                    Select(g => Observable.Defer(source.Materialize)). // get the result of the selector
                    Switch(). // always take the last one
                    Do((v) =>
                    {
                        switch (v.Kind)
                        {
                            case NotificationKind.OnNext:
                                o.OnNext(v.Value);
                                break;

                            case NotificationKind.OnError:
                                ++retryCount;
                                trySubject.OnNext(v.Exception);
                                break;

                            case NotificationKind.OnCompleted:
                                trySubject.OnCompleted();
                                break;
                        }
                    }
                    ).Subscribe(_ => { }, o.OnError, o.OnCompleted);
            });
        }
//
///使用单独的可观察对象重试源,以确定是否再次重试。
/// 
/// 
/// 
///用于确定是否重试的可观察工厂。作为参数提供的重试次数和异常
///用于在上观察源的计划程序。如果使用非指定的MainThreadScheduler
///用于在上观察重试的计划程序。如果使用了未指定的MainThreadScheduler。
/// 
公共静态IObservable RetryX(此IObservable源,
Func retryObservable,IsScheduler executeScheduler=null,
IsScheduler retryScheduler=null)
{
if(retryObservable==null)
{
抛出新ArgumentNullException(nameof(retryObservable));
}
if(executeScheduler==null)
{
executeScheduler=MainScheduler;
}
如果(retryScheduler==null)
{
retryScheduler=MainScheduler;
}
//所以,我们需要订阅序列,如果我们得到一个错误,那么我们再做一次。。。
返回可观察的。创建(o=>
{
//当我们应该运行时,我们需要执行此操作
var trySubject=新主题();
//记录我们重试的次数
var-retryCount=0;
返回trySubject。
AsObservable()。
ObserveOn(retryScheduler)。
SelectMany(e=>Observable.Defer(()=>retryObservable(retryCount,e))。//选择重试逻辑
StartWith(true)。//充注泵以确保至少执行一次
TakeWhile(shouldTry=>shouldTry)。//同时我们应该再试一次
ObserveOn(执行调度器)。
选择(g=>Observable.Defer(source.Materialize))。//获取选择器的结果
开关()。//始终使用最后一个开关
Do((v)=>
{
开关(v.Kind)
{
案例通知种类.OnNext:
o、 OnNext(v.Value);
打破
案例通知Kind.OnError:
++复述计数;
trySubject.OnNext(v.Exception);
打破
案例通知种类.未完成:
trySubject.OnCompleted();
打破
}
}
).Subscribe({},o.OnError,o.OnCompleted);
});
}

问题在于没有将ISScheduler正确地传递给RetryX扩展方法和
Observable.Timer

public static IObservable<T> RetryWithCount<T>(this IObservable<T> source, 
            int retryCount, int delayMillisecondsToRetry, IScheduler executeScheduler = null,
            IScheduler retryScheduler = null)
        {
            if (executeScheduler == null)
            {
                executeScheduler = MainScheduler;
            }
            var retryAgain = retryCount + 1;
            return source
                .RetryX(
                    (retry, exception) =>
                    {
                        return retry == retryAgain
                            ? Observable.Throw<bool>(exception, executeScheduler)
                            : Observable.Timer(TimeSpan.FromMilliseconds(delayMillisecondsToRetry), executeScheduler)
                                .Select(_ => true);
                    },
                    retryScheduler,
                    executeScheduler);
        }
公共静态IObservable RetryWithCount(此IObservable源,
int-retryCount,int-delaymillissecondstoretry,isScheduler-executeScheduler=null,
IsScheduler retryScheduler=null)
{
if(executeScheduler==null)
{
executeScheduler=MainScheduler;
}
var retryreach=retryCount+1;
返回源
雷特里克斯先生(
(重试,异常)=>
{
return retry==再次重试
?可观察的抛出(异常,executeScheduler)
:Observable.Timer(TimeSpan.From毫秒(DelayMillisSecondStoreTry),executeScheduler)
.选择(=>true);
},
retryScheduler,
执行调度员);
}

这不是对你问题的回答,而是一些可能对你有帮助的东西:我看了一会儿
RetryX
,如果你去掉所有
调度程序
的东西,你可能应该这样做,它可以简化为:

public static IObservable<T> RetryX<T>(this IObservable<T> source, Func<int, Exception, IObservable<bool>> retryObservable)
{
    return source.Catch((Exception e) => retryObservable(1, e)
        .Take(1)
        .SelectMany(b => b ? source.RetryX((count, ex) => retryObservable(count + 1, ex)) : Observable.Empty<T>()));
}
publicstaticiobservable-RetryX(此IObservable源,Func-retryObservable)
{
返回source.Catch((异常e)=>retryObservable(1,e)
.采取(1)
.SelectMany(b=>b?source.RetryX((count,ex)=>retryObservable(count+1,ex)):Observable.Empty());
}
并非所有调度程序调用都是“最佳实践”。大多数Rx运营商不接受调度程序参数是有原因的(
Select
Where
Catch
,等等)。那些与时间/进度有关的