【问题标题】:How to capture last trial result in Poly retry policy?如何在 Polly 重试策略中捕获最后的试验结果?
【发布时间】:2022-02-22 23:00:34
【问题描述】:

我正在努力扩展 .NET Core 中的 TestMethod 属性。我将 Polly 库用于重试逻辑以及外部超时策略。

我想要一个可以重试调用ITestMethod 直到它通过的辅助方法。我不介意它会被重试多少次。但我会设置一个必须在其中完成的超时。如果委托在超时内成功执行,那就没问题了。但是如果出现超时异常,我还是要失败的结果值(最后一次迭代的结果)而不是TimeOutRejectedException或者返回类型的默认值。

下面是我扩展的测试方法属性类:

public sealed class GuaranteedPassTestMethodAttribute : TestMethodAttribute
{
    /// <inheritdoc/>
    public override TestResult[] Execute(ITestMethod testMethod)
    {
        return ExecuteTestTillSuccess(testMethod);
    }

    private TestResult[] ExecuteTestTillSuccess(ITestMethod testMethod)
    {
        var gracefulTestRun =
            TestExecutionHelper.ExecuteTestTillPassAsync(
                () => TestInvokeDelegate(testMethod));

        return gracefulTestRun.Result;
    }

    private Task<TestResult[]> TestInvokeDelegate(ITestMethod testMethod)
    {
        TestResult[] result = null;
        var thread = new Thread(() => result = Invoke(testMethod));
        thread.Start();
        thread.Join();

        return Task.FromResult(result);
    }
}

下面是我使用 Polly 的TestExecutionHelper

internal static class TestExecutionHelper
{
    private static readonly Func<TestResult[], bool> TestFailurePredicate =
        results => results != null &&
                   results.Length == 1 &&
                   results.First().Outcome != UnitTestOutcome.Passed;

    internal static async Task<TestResult[]> ExecuteTestTillPassAsync(
        Func<Task<TestResult[]>> testInvokeDelegate,
        int delayBetweenExecutionInMs = 3000,
        int timeoutInSeconds = 60 * 10)
    {
        var timeoutPolicy = Policy.TimeoutAsync<TestResult[]>(timeoutInSeconds);
        var retryPolicy = Policy.HandleResult<TestResult[]>(TestFailurePredicate)
                                .WaitAndRetryAsync(int.MaxValue, x => TimeSpan.FromMilliseconds(delayBetweenExecutionInMs));
        var testRunPolicy = timeoutPolicy.WrapAsync(retryPolicy);
        return await testRunPolicy.ExecuteAsync(testInvokeDelegate);
    }
}

使用此设置,我要么获得通过的测试方法执行,要么获得 TimeOutRejectedException 用于失败的测试。即使在重试之后,我也想捕获失败的测试 TestResult

【问题讨论】:

  • 能否请您详细说明此代码Invoke(testMethod)?我可以testMethod.Invoke 确实存在,但它返回一个TestResult

标签: c# unit-testing .net-core polly


【解决方案1】:

测试用例

假设我们有以下测试:

[TestClass]
public class UnitTest1
{
    private static int counter;
    [GuaranteedPassTestMethod]
    public async Task TestMethod1()
    {
        await Task.Delay(1000);
        Assert.Fail($"Failed for {++counter}th time");
    }
}

我使用了一个static 变量(称为counter)来更改每次测试运行的输出。

属性

我已经通过使用Task.Run 简化了您的属性代码:

public sealed class GuaranteedPassTestMethodAttribute : TestMethodAttribute
{
    public override TestResult[] Execute(ITestMethod testMethod)
        => TestExecutionHelper
            .ExecuteTestTillPassAsync(
                async () => await Task.Run(
                    () => testMethod.Invoke(null)))
            .GetAwaiter().GetResult();
}

我还将.Result 更改为GetAwaiter().GetResult() 以便更好地处理异常。

  • 如果您不熟悉此模式,请阅读this topic

策略

我引入了一个名为 Results 的累加器变量,我在其中捕获所有测试运行的结果。

internal static class TestExecutionHelper
{
    private static readonly List<TestResult> Results = new List<TestResult>();
    private static readonly Func<TestResult, bool> TestFailurePredicate = result =>
    { 
        Results.Add(result);
        return result != null && result.Outcome != UnitTestOutcome.Passed;
    };

    internal static async Task<TestResult[]> ExecuteTestTillPassAsync(
        Func<Task<TestResult>> testInvokeDelegate,
        int delayBetweenExecutionInMs = 3000,
        int timeoutInSeconds = 1 * 10)
    {
        var timeoutPolicy = Policy.TimeoutAsync<TestResult>(timeoutInSeconds);
        var retryPolicy = Policy.HandleResult(TestFailurePredicate)
            .WaitAndRetryForeverAsync(_ => TimeSpan.FromMilliseconds(delayBetweenExecutionInMs));
        var testRunPolicy = timeoutPolicy.WrapAsync(retryPolicy);

        try { await testRunPolicy.ExecuteAsync(testInvokeDelegate); }
        catch (TimeoutRejectedException) { } //Suppress

        return Results.ToArray();
    }
}
  • 我在这里使用了WaitAndRetryForeverAsync 而不是WaitAndRetryAsync(int.MaxValue, ...
  • 我还更改了testInvokeDelegate 的签名以与界面对齐。

输出

对于我的测试,我将timeoutInSeconds 的默认值减少到 10 秒。

 TestMethod1
   Source: UnitTest1.cs line 17

Test has multiple result outcomes
   3 Failed

Results

    1)  TestMethod1
      Duration: 1 sec

      Message: 
        Assert.Fail failed. Failed for 1th time
      Stack Trace: 
        UnitTest1.TestMethod1() line 20
        ThreadOperations.ExecuteWithAbortSafety(Action action)

    2)  TestMethod1
      Duration: 1 sec

      Message: 
        Assert.Fail failed. Failed for 2th time
      Stack Trace: 
        UnitTest1.TestMethod1() line 20
        ThreadOperations.ExecuteWithAbortSafety(Action action)

    3)  TestMethod1
      Duration: 1 sec

      Message: 
        Assert.Fail failed. Failed for 3th time
      Stack Trace: 
        UnitTest1.TestMethod1() line 20
        ThreadOperations.ExecuteWithAbortSafety(Action action)

让我们看看这次测试运行的时间线:

  • 0 >> 1:TestMethod1Delay
  • 1:Assert.Fail
  • 1 >> 4:重试的惩罚
  • 4 >> 5:TestMethod1Delay
  • 5:Assert.Fail
  • 5 >> 8:重试的惩罚
  • 8 >> 9:TestMethod1Delay
  • 9:Assert.Fail
  • 9 >> 12: : 重试的惩罚
  • 10:超时开始

这里需要注意一件事:Test Duration 不会包含重试的惩罚延迟:

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-10-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多