簡體   English   中英

單元測試睡眠持續時間 polly 等待重試策略

[英]Unit test sleep durations polly wait retry policy

我想編寫一個單元測試來查看執行是否在指定的持續時間內處於休眠狀態。 我遇到了屬於Polly.Utilities<\/code>的SystemClock<\/code> ,但我正在尋找類似於此處WaitAndRetrySpecs<\/code><\/a> Polly 單元測試的實現WaitAndRetrySpecs<\/code><\/a> ,看起來像這樣

 [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);
  }

您可以通過使用onRetry函數來實現這一點。

為了簡單IsTransientError ,讓我定義IsTransientErrorGetSleepDurationByRetryAttempt方法,如下所示:

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。 一個具有以下簽名的重載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, duration, 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 :減少延誤並介紹理論

根據您的要求,使用不同的參數運行相同的測試用例可能是有意義的。 這就是TheoryInlineData可以幫助您的地方:

[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, GetSleepDurationByRetryAttempt,
            onRetry: (ex, duration, 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的傳輸和檢索更加類型安全,我們可以為此創建兩個擴展方法:

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;
    }
}

我們還可以提取 Policy 創建邏輯:

private AsyncPolicy GetCustomPolicy(int retryCount)
    => Policy
        .Handle<SqlException>(IsTransientError)
        .WaitAndRetryAsync(retryCount, GetSleepDurationByRetryAttempt,
            onRetry: (ex, duration, ctx) =>
            {
                var totalSleepDuration = ctx.GetAccumulator();
                if (!totalSleepDuration.HasValue) return;
                totalSleepDuration = totalSleepDuration.Value.Add(duration);
                ctx.SetAccumulator(totalSleepDuration.Value);
            });

現在讓我們把所有這些放在一起(再一次):

[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 = 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);
}

測試不起作用,因為您使用的是 WaitAndRetryAsync 而不是 WaitAndRetry。 我有一個類似的問題並通過使用修復

 SystemClock.SleepAsync = (s, cts) =>
        {
            totalTimeSlept += s.Seconds;
            return Task.CompletedTask;
        };

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM