如何解决单元测试睡眠时间Polly等待重试策略
我想编写一个单元测试,以查看执行是否在指定的持续时间内处于休眠状态。我遇到了SystemClock
的一部分Polly.Utilities
,但是我正在寻找类似于此处WaitAndRetrySpecs
提到的Polly单元测试的实现,它看起来像这样
[Fact]
public void Should_sleep_for_the_specified_duration_each_retry_when_specified_exception_thrown_same_number_of_times_as_there_are_sleep_durations()
{
var totalTimeSlept = 0;
var policy = Policy
.Handle<DivideByZeroException>()
.WaitAndRetry(new[]
{
1.Seconds(),2.Seconds(),3.Seconds()
});
SystemClock.Sleep = span => totalTimeSlept += span.Seconds;
policy.RaiseException<DivideByZeroException>(3);
totalTimeSlept.Should()
.Be(1 + 2 + 3);
}
当前我的政策如下
var customPolicy = Policy
.Handle<sqlException>(x => IsTransientError(x))
.WaitAndRetryAsync(
3,(retryAttempt) => getSleepDurationByRetryAtempt(retryAttempt)
);
我想测试政策制定的总体时间。对于每次重试尝试[1,2,3],睡眠时间为[1,3]。 在全部3次重试之后,总睡眠时间应为1 + 2 + 3 =6。此测试与上面链接中提到的Polly规格非常相似。
问题::我如何为customPolicy编写单元测试,以测试类似于Polly规范的总睡眠时间。我想看一个实现或编写单元测试的说明。
解决方法
您可以通过使用onRetry
函数来实现此目的。
为简单起见,让我定义如下的IsTransientError
和GetSleepDurationByRetryAttempt
方法:
public TimeSpan GetSleepDurationByRetryAttempt(int attempt) => TimeSpan.FromSeconds(attempt);
public bool IsTransientError(SqlException ex) => true;
顺便说一句,您可以通过避免(不必要的)匿名lambda来缩短策略定义:
var customPolicy = Policy
.Handle<SqlException>(IsTransientError)
.WaitAndRetryAsync(3,GetSleepDurationByRetryAttempt)
因此,回到onRetry。 an overload具有以下签名:Action<Exception,TimeSpan,Context>
。这里的第二个参数是睡眠时间。
我们要做的就是在此处提供一个功能,该功能可以累积睡眠时间。
var totalSleepDuration = TimeSpan.Zero;
...
onRetry: (ex,duration,ctx) => { totalSleepDuration = totalSleepDuration.Add(duration); }
我们将所有这些放在一起:
[Fact]
public async Task GivenACustomSleepDurationProvider_WhenIUseItInARetryPolicy_ThenTheAccumulatedDurationIsAsExpected()
{
//Arrange
var totalSleepDuration = TimeSpan.Zero;
var customPolicy = Policy
.Handle<SqlException>(IsTransientError)
.WaitAndRetryAsync(3,GetSleepDurationByRetryAttempt,onRetry: (ex,ctx) => { totalSleepDuration = totalSleepDuration.Add(duration); }
);
//Act
Func<Task> actionWithRetry = async() => await customPolicy.ExecuteAsync(() => throw new SqlException());
//Assert
_ = await Assert.ThrowsAsync<SqlException>(actionWithRetry);
Assert.Equal(6,totalSleepDuration.Seconds);
}
更新#1 :减少延迟并介绍理论
根据您的要求,可能需要使用不同的参数运行相同的测试用例。这是Theory
和InlineData
可以为您提供帮助的地方:
[Theory]
[InlineData(3,600)]
[InlineData(4,1000)]
[InlineData(5,1500)]
public async Task GivenACustomSleepDurationProvider_WhenIUseItInARetryPolicy_ThenTheAccumulatedDurationIsAsExpected(int retryCount,int expectedTotalSleepInMs)
{
//Arrange
var totalSleepDuration = TimeSpan.Zero;
var customPolicy = Policy
.Handle<SqlException>(IsTransientError)
.WaitAndRetryAsync(retryCount,ctx) => { totalSleepDuration = totalSleepDuration.Add(duration); }
);
//Act
Func<Task> actionWithRetry = async () => await customPolicy.ExecuteAsync(() => throw new SqlException());
//Assert
_ = await Assert.ThrowsAsync<SqlException>(actionWithRetry);
Assert.Equal(TimeSpan.FromMilliseconds(expectedTotalSleepInMs),totalSleepDuration);
}
public static TimeSpan GetSleepDurationByRetryAttempt(int attempt) => TimeSpan.FromMilliseconds(attempt * 100);
更新#2 :通过Context
为了进行TimeSpan
a bit more type-safe的传输和检索,我们可以为此创建两个扩展方法:
public static class ContextExtensions
{
private const string Accumulator = "DurationAccumulator";
public static Context SetAccumulator(this Context context,TimeSpan durationAccumulator)
{
context[Accumulator] = durationAccumulator;
return context;
}
public static TimeSpan? GetAccumulator(this Context context)
{
if (!context.TryGetValue(Accumulator,out var ts))
return null;
if (ts is TimeSpan accumulator)
return accumulator;
return null;
}
}
我们还可以提取策略创建逻辑:
private AsyncPolicy GetCustomPolicy(int retryCount)
=> Policy
.Handle<SqlException>(IsTransientError)
.WaitAndRetryAsync(retryCount,ctx) =>
{
var totalSleepDuration = ctx.GetAccumulator();
if (!totalSleepDuration.HasValue) return;
totalSleepDuration = totalSleepDuration.Value.Add(duration);
ctx.SetAccumulator(totalSleepDuration.Value);
});
现在让我们将所有这些放在一起(再次):
[Theory]
[InlineData(3,1500)]
public async Task GivenACustomSleepDurationProvider_WhenIUseItInARetryPolicy_ThenTheAccumulatedDurationIsAsExpected(
int retryCount,int expectedTotalSleepInMs)
{
//Arrange
var totalSleepDuration = TimeSpan.Zero;
var customPolicy = GetCustomPolicy(retryCount);
var context = new Context().SetAccumulator(totalSleepDuration);
//Act
Func<Task> actionWithRetry = async () => await customPolicy.ExecuteAsync(ctx => throw new SqlException(),context);
//Assert
_ = await Assert.ThrowsAsync<SqlException>(actionWithRetry);
var accumulator = context.GetAccumulator();
Assert.NotNull(accumulator);
Assert.Equal(TimeSpan.FromMilliseconds(expectedTotalSleepInMs),accumulator.Value);
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。