如何解决如何在 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:
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:超时开始
这里有一点需要注意:测试持续时间不会包含重试的惩罚延迟:
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。