MSTest / VSTest 重试(重新运行)逻辑

3

很遗憾,MStest / VStest没有本地测试重跑逻辑。

我正在尝试实现类似于这样的自定义逻辑:

测试部分:

    static int testNum = 1;

    [TestMethod]
    public void RerunTestOnce_Test()
    {
        testNum = testNum + 1;
        Console.WriteLine("Test started");
        Assert.IsTrue(testNum == 3, $"Test Failed with number {testNum}");

    }

第一次测试应该失败,第二次当testNum达到3时测试应该通过。

更新:这是一个人工制作的示例,目的是在第一次运行时模拟失败。实际测试非常复杂,包括UI搜索方法和其他与系统和网络的交互,并且在大型长时间测试套件期间没有信心一切都会顺利。

有一个特殊的方法可以解决这个问题 - 在TestCleanup中调用RerunTestOnce()

    [TestCleanup]
    public void TestCleanup()
    {
        TestHelper.RerunTestOnce(TestContext, this);
    }

以下是测试辅助类中RerunTestOnce方法的实现。在此方法中,我们使用反射和TestContext获取测试方法和初始化方法的名称,并再次运行它们:

 public static void RerunTestOnce(TestContext testContext, object testInstance)
    {
        if (testContext.CurrentTestOutcome == UnitTestOutcome.Failed)
        {
            var type = testInstance.GetType();
            if (type != null)
            {
                var testMethod = type.GetMethod(testContext.TestName);
                var initMethod = type.GetMethods().SingleOrDefault(m=>m.CustomAttributes.SingleOrDefault(a=>a.AttributeType.Name == "TestInitializeAttribute")!= null);
                var cleanupMethod = type.GetMethods().SingleOrDefault(m => m.CustomAttributes.SingleOrDefault(a => a.AttributeType.Name == "TestCleanupAttribute") != null);

                Console.WriteLine($"[WARNING] Method [{testMethod}] was failed in first attempt. Trying to rerun...");
                try
                {
                    initMethod.Invoke(testInstance, null);
                    testMethod.Invoke(testInstance, null);
                }
                catch
                {
                    Console.WriteLine($"[ERROR] Method [{testMethod}] was failed in second attempt. Rerun finished.");
                }
            }
        }
    }

一切都没问题,在第二次测试方法通过,但最后我看到了第一次尝试的失败结果和断言错误信息:

Test Failed - RerunTestOnce_Test
Message: Assert.IsTrue failed. Test Failed with number 2

MSTest 是何时创建测试结果的?第二次尝试后是否可以更新上一次结果?


1
为什么要跳过所有这些步骤?“这应该做三次,第一次成功,第二次失败”是您代码逻辑的一部分,并且应该在测试本身中清楚地表达,而不是作为测试方法的重新运行。您可以使用类似于Fluent Assertions的东西来简洁地表达这个(.Should().[Not]Throw()),并将整个内容包装为一个接受Action委托的方法,如果您需要重用此逻辑。 - Jeroen Mostert
Jeroen Mostert测试预期始终通过。这只是一个合成示例,以模拟第一次运行失败。因此,如果由于某种原因运行失败,则需要重新运行整个测试,仅当第二次失败时,结果应为失败。 - Vladimir
2
考虑修复代码,使其第一次不会失败。如果测试足够脆弱以至于“可能在第一次失败”,那就不是好事!你应该把这当作一个机会,让代码变得足够可靠以进行测试(通过等待服务器可用或类似的方式),而不是作为改变测试的动力。如果你真的需要这个功能,你应该设置一个外部测试运行器/构建服务器,可以调用整个测试集(例如使用vstest.console.exe),并根据任何测试是否失败来应用此“重试逻辑”。 - Jeroen Mostert
再次强调,这个例子是人工合成的。它并不是“单元测试”。真正的测试是复杂的,需要使用UI搜索方法以及其他与系统和网络相关的工作。在一个大型的测试套件中,无法百分之百地保证在测试执行期间一切都会顺利进行。这就是为什么一些先进的测试框架,如NUnit,已经实现了“重试”逻辑。不幸的是,我们使用的编码UI测试与NUnit不兼容。 - Vladimir
考虑只使用两个测试项目--MSTest用于编码UI,NUnit或其他您喜欢的工具用于另一个项目。否则,您可能会一直试图把方形钉子塞进圆孔里。您可能需要一些管道来使两个测试项目在一个解决方案中运行,但这仍然比尝试从根本上更改MSTest要简单。如果您被困在MSTest中(无论出于何种原因),一个选择可能是使用T4模板自动生成测试类的脚手架,以便可以重试它们而无需编写代码。您可以在测试上下文中记录额外的尝试。 - Jeroen Mostert
顺便提一下,你可能想尝试MSTestEx,这是MSTest测试框架的一组扩展。目前在NuGet上发布的版本(v1.0.2)已经支持“重试”。欢迎反馈和贡献。 - pvlakshm
2个回答

2

更新

今天我学到了你实际上可以编写自己的TestMethod属性。

默认的实现看起来像这样

public class TestMethodAttribute : Attribute
{
    public virtual TestResult[] Execute(ITestMethod testMethod)
    {
        return new TestResult[1] { testMethod.Invoke(null) };
    }
}

所以你实际上可以创建自己的TestMethodWithRetry属性,并在方法上使用它。
[TestMethodWithRetry]
public void TestRetry()
{
    var x = new Random().Next(0, 2);
    Assert.AreEqual(1, x);
}

// or
[TestMethodWithRetry(Count = 10)]
public void TestRetry()
{
    var x = new Random().Next(0, 2);
    Assert.AreEqual(1, x);
}

// even works with DataRow
[TestMethodWithRetry(Count = 10)]
[DataRow(2)]
[DataRow(5)]
[DataRow(10)]
public void TestRetry(int max)
{
    var x = new Random().Next(0, max);
    Assert.AreEqual(1, x);
}

public class TestMethodWithRetryAttribute : TestMethodAttribute
{

    public int Count { get; set; } = 1;

    public override TestResult[] Execute(ITestMethod testMethod)
    {
        var count = Count;
        TestResult[] result = null;
        while (count > 0)
        {
            try
            {
                result = base.Execute(testMethod);
                if (result[0].TestFailureException != null)
                {
                    throw result[0].TestFailureException;
                }
            }
            catch (Exception) when (count > 0)
            {
            }
            finally
            {
                count--;
            }
        }
        return result;
    }

}

这只是一个简单的实现,但它似乎出奇地工作得很好。
我想出了以下的解决方案。
public static void Retry(Action test, int retry = 10, int sleep = 0, [CallerMemberName] string testName = null)
{
    int current = 1;
    retry = Math.Max(1, Math.Min(retry, 10));
    while (current <= retry)
    {
        try
        {
            test();
            break;
        }
        catch (Exception ex) when (current < retry)
        {
            Debug.WriteLine("Test {0} failed ({1}. try): {2}", testName, current, ex);
        }

        if (sleep > 0)
        {
            Thread.Sleep(sleep);
        }
        current++;
    }
}

使用方法

[TestMethod]
public void CanRollbackTransaction()
{
    Helpers.Retry(() =>
    {
        var even = DateTime.Now.Second % 2 == 0;
        Assert.IsTrue(even);
    }, 3, 1000);
}

1

1
MsTestEx的“Retry”属性不会改变整个测试的结果,因此它对于在CI流水线中失败的某些测试没有帮助。 - Jürgen Steinblock

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接