xunit测试事实多次

38

我有一些方法依赖于一些随机计算来提供建议,我需要多次运行这个Fact以确保它可行。

我可以在要测试的fact中包含一个for循环,但由于有几个测试我想这样做,所以我在寻找一种更干净的方法,类似于junit中的Repeat属性:http://www.codeaffine.com/2013/04/10/running-junit-tests-repeatedly-without-loops/

我能否在xunit中轻松实现类似的功能?

6个回答

47

为了让xunit运行同一个测试多次,您需要创建一个新的DataAttribute

这里是一个遵循junit相同思路的示例:

public class RepeatAttribute : DataAttribute
{
    private readonly int _count;

    public RepeatAttribute(int count)
    {
        if (count < 1)
        {
            throw new ArgumentOutOfRangeException(nameof(count), 
                  "Repeat count must be greater than 0.");
        }
        _count = count;
    }

    public override IEnumerable<object[]> GetData(MethodInfo testMethod)
    {
        return Enumerable.Repeat(new object[0], _count);
    }
}

有了这段代码,你只需要将Fact更改为Theory,并像这样使用Repeat

[Theory]
[Repeat(10)]
public void MyTest()
{
    ...
}

运行得很好。不过我必须从GetData中删除Type[] parameterTypes参数。现在如果这可以与其他属性一起使用,比如CombinatorialData就好了。 - gligoran
我已更新示例代码以反映较新的xunit API。谢谢! - Ken Smith
我已更新代码,使其与最新的xunit兼容。只要repetitionNumber是Theory测试的参数,它就可以正常工作。 - mbabramo
2
似乎不起作用 @user357783,你能给个例子吗? - Ywapom
这些重复的操作可以并行运行吗? - Matt W
显示剩余3条评论

28

虽然我有同样的需求,但是被接受的答案代码并没有重复测试,所以我对其进行了修改:

public sealed class RepeatAttribute : Xunit.Sdk.DataAttribute
{
    private readonly int count;

    public RepeatAttribute(int count)
    {
        if (count < 1)
        {
            throw new System.ArgumentOutOfRangeException(
                paramName: nameof(count),
                message: "Repeat count must be greater than 0."
                );
        }
        this.count = count;
    }

    public override System.Collections.Generic.IEnumerable<object[]> GetData(System.Reflection.MethodInfo testMethod)
    {
        foreach (var iterationNumber in Enumerable.Range(start: 1, count: this.count))
        {
            yield return new object[] { iterationNumber };
        }
    }
}

虽然在之前的示例中使用了Enumerable.Repeat,但它只会运行1次测试,某种方式xUnit没有重复测试。可能是他们一段时间以前改变了什么。 通过更改为foreach循环,我们可以重复每个测试,但也提供了“迭代号”。 当在测试函数上使用它时,您必须向测试函数添加一个参数并将其装饰为Theory,如下所示:

[Theory(DisplayName = "It should work")]
[Repeat(10)]
public void It_should_work(int iterationNumber)
{
...
}

这适用于 xUnit 2.4.0。

我创建了一个 NuGet ,以便在有兴趣的情况下使用此功能。


感谢您注意到这一点,@ShanteshwarInde。 - MarcolinoPT
我有一个功能建议,即测试缓存旁路模式的[Theory]。第一次运行将向分布式缓存中插入数据,第二次从缓存中提取先前的数据,但是您的NuGet似乎无法正确处理[InlineData]属性,即[InlineData("myCacheKey")](而且VS2k19 16.3中的UI有点混乱)... :) - alv
谢谢您的反馈@alv,但那似乎是xUnit的一个功能请求...;-) - MarcolinoPT
GetData 可以简化为:return Enumerable.Range(1, this.count).Select(n => new object[] { n }) - oatsoda

8

对于少量迭代的最简单的方法:将其作为理论而不是事实。 为每个迭代插入一行 [InlineData]

using Xunit;

namespace MyUnitTests
{
    public class Class1
    {

        [Theory]
        [InlineData]
        [InlineData]
        public void TestThis()
        {
            // test code here
        }
    }
}

已经使用 XUnit 2.4.1 进行测试。


1
为了忽略编译器错误,请将以下内容添加到方法中: `[SuppressMessage("Usage", "xUnit1025:InlineData should be unique within the Theory it belongs to")] [SuppressMessage("Usage", "xUnit1006:Theory methods should have parameters")]` - Ofiris

5

我知道这个帖子有点旧了,但如果您需要重复进行少量测试,可以使用以下小技巧:

首先,创建一些虚拟数据:

public static IEnumerable<object[]> DummyTestData()
{
   yield return new object[] { 0 };
   yield return new object[] { 1 };
   yield return new object[] { 2 };
   yield return new object[] { 3 };
 }

然后使用虚拟数据来强制测试对每个向量运行。在这种情况下,同一测试将被调用4次(但实际上未使用虚拟数据):

private static int _counter = 0;

[Theory]
[MemberData(nameof(DummyTestData))]
public void SomeTest(int dummyParam)     // dummyParam is unused
{
    _counter+= 1;
    DoSomething();

    Assert.True(...);           
}    

我认为这种方法比创建新属性更有用且不那么麻烦。 当然,如果您需要将重复次数参数化,这并不是一个好的解决方案(尽管我相信有人可以建议一种方法来使我的解决方案可参数化 :-)).

1

由于MemberData还需要成员参数,因此您可以简单地传递迭代号以创建“DummyTestData”。 {{link1:在此输入图像描述}}

public static IEnumerable<object[]> MockParallel(int n)
{
    foreach (var iterationNumber in Enumerable.Range(start: 1, count: n))
    {
        yield return new object[] { iterationNumber };
    }
}

现在你可以将重复次数作为参数传递。
[Theory]
[MemberData(nameof(MockParallel), n)] // repeat n times
public void SomeTest(int repeat)

0

您可以使用ITestCaseOrderer来对测试进行排序。在排序时,您可以多次指定某些测试。例如:

using System.Collections.Generic;
using System.Linq;
using Xunit.Abstractions;
using Xunit.Sdk;

namespace XUnit.Project.Orderers {
    public class RepeatOrderer : ITestCaseOrderer {
        public IEnumerable<TTestCase> OrderTestCases<TTestCase>(IEnumerable<TTestCase> testCases) where TTestCase : ITestCase {
            List<TTestCase> result = new();
            testCases.ToList().ForEach(testCase => {
                var repeat = (ReflectionAttributeInfo)testCase.TestMethod.Method.GetCustomAttributes(typeof(RepeatAttribute)).FirstOrDefault();
                if (repeat != null && repeat.Attribute is RepeatAttribute) {
                    var attr = repeat.Attribute as RepeatAttribute;
                    Enumerable.Range(0, attr.Count).ToList().ForEach((_) => { result.Add(testCase); });
                }
                else {
                    result.Add(testCase);
                }
            });

            return result;
        }
    }
}

属性

public class RepeatAttribute : Attribute {
    public int Count { get; init; }
    public RepeatAttribute(int count) {
        this.Count = count;
    }
}

指示测试项目使用排序器。在我的例子中,TestProject1 是包含排序器的程序集。

[assembly : TestCaseOrderer("XUnit.Project.Orderers.RepeatOrderer", "TestProject1")]

并使用Repeate属性

[Fact]
[Repeat(6)]
public void Test1() {

...
}


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