如何在 Polly 重试策略中捕获最后一次试验结果?
How to capture last trial result in Poly retry policy?
我正在努力扩展 .NET Core 中的 TestMethod
属性。我将 Polly 库用于重试逻辑以及外部超时策略。
我想要一个可以重试调用 ITestMethod
直到它通过的辅助方法。我不介意重试的次数。但我会设置一个超时时间,必须在该时间内完成。如果delegate在超时时间内执行成功,就可以了。但是如果出现超时异常,我还是想要失败的结果值(最后一次迭代的结果)而不是TimeOutRejectedException
或者return类型的默认值
下面是我的扩展测试方法属性class:
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:
TestMethod1
的 Delay
- 1:
Assert.Fail
- 1 >> 4:重试的惩罚
- 4 >> 5:
TestMethod1
的 Delay
- 5:
Assert.Fail
- 5 >> 8:重试的惩罚
- 8 >> 9:
TestMethod1
的 Delay
- 9:
Assert.Fail
- 9 >> 12: : 重试惩罚
- 10:超时开始
这里需要注意一件事:测试持续时间不包含重试的惩罚延迟:
我正在努力扩展 .NET Core 中的 TestMethod
属性。我将 Polly 库用于重试逻辑以及外部超时策略。
我想要一个可以重试调用 ITestMethod
直到它通过的辅助方法。我不介意重试的次数。但我会设置一个超时时间,必须在该时间内完成。如果delegate在超时时间内执行成功,就可以了。但是如果出现超时异常,我还是想要失败的结果值(最后一次迭代的结果)而不是TimeOutRejectedException
或者return类型的默认值
下面是我的扩展测试方法属性class:
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:
TestMethod1
的Delay
- 1:
Assert.Fail
- 1 >> 4:重试的惩罚
- 4 >> 5:
TestMethod1
的Delay
- 5:
Assert.Fail
- 5 >> 8:重试的惩罚
- 8 >> 9:
TestMethod1
的Delay
- 9:
Assert.Fail
- 9 >> 12: : 重试惩罚
- 10:超时开始
这里需要注意一件事:测试持续时间不包含重试的惩罚延迟: