微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

如何在 Poly 重试策略中捕获最后一次试验结果? 测试用例属性战略输出

如何解决如何在 Poly 重试策略中捕获最后一次试验结果? 测试用例属性战略输出

我正在努力扩展 .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

解决方法

测试用例

假设我们有以下测试:

[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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。