在dotnet core中使用Xunit按顺序执行测试

4
我想按顺序运行我的测试,因为它们会更改同一个数据库,可能会相互影响。我在互联网上尝试了很多解决方案,但都不适用于我。这些解决方案在链接“按顺序执行单元测试(而不是并行执行)”中有描述。 我现在有点困扰。有人对这个问题有什么想法吗?
2个回答

6

来自文档:

默认情况下,每个测试类都是唯一的测试集合。
相同测试类中的测试用例不会相互并行运行。

对于来自不同类的测试用例

如果我们需要表明多个测试类不应该互相并行运行,那么我们就将它们放入同一个测试集合中。
这只需要为每个测试类添加一个属性即可,该属性将它们放入同一个具有唯一名称的测试集合中。

[Collection("Database tests")]
public class DeleteTests
{
    [Fact]
    public void RemovesItemFromDatabase()
    {
        // test
    }
}

[Collection("Database tests")]
public class InsertTests
{
    [Fact]
    public void InsertsItemToDatabase()
    {
        // test
    }
}

我尝试了那种方法,但是装饰器对我没有起作用。同一类中的测试仍然在并行运行。 - Thanh Binh Nguyen
@ThanhBinhNguyen,嗯,我怀疑这是这样的情况。您可能需要分享您的代码并解释您如何观察到同一类中的测试是并行执行的。 - Fabio
不幸的是,由于我的测试需要与多个Docker程序进行交互,因此在此处放置代码并不容易。为了观察执行情况,我在每个测试之前和之后放置了调试消息。我发现有时一个测试还没有完成,另一个测试就进入了执行过程。这就是我观察到的并行性。尽管如此,在我撰写这条消息的时候,我已经成功地强制以顺序方式执行我的测试(但不使用上述描述的装饰)。到目前为止,我还没有注意到任何并行行为,所以希望我做得正确。 - Thanh Binh Nguyen
3
Thanh Binh Nguyen,你最终采用了哪个解决方案? - ssmith
事实上,尽管这也在xunit的官方文档中有提到,但似乎并没有起作用。 - undefined

0
要使用自定义属性对xUnit测试进行排序,首先需要有一个可依赖的属性。请按以下方式定义PriorityAttribute:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class PriorityAttribute : Attribute
{
    public PriorityAttribute(int priority)
    {
        Priority = priority;
    }

    public int Priority { get; private set; }
}

接下来,考虑以下实现了ITestCaseOrderer接口的PriorityOrderer。
public class PriorityOrderer : ITestCaseOrderer
{
    public const string Name = "Namespace.PriorityOrderer";
    public const string Assembly = "Assembly name";

    private static string _priorityAttributeName = typeof(PriorityAttribute).AssemblyQualifiedName;
    private static string _priorityArgumentName = nameof(PriorityAttribute.Priority);

    private static ConcurrentDictionary<string, int> _defaultPriorities = new ConcurrentDictionary<string, int>();

    public IEnumerable<TTestCase> OrderTestCases<TTestCase>(IEnumerable<TTestCase> testCases) where TTestCase : ITestCase
    {
        var groupedTestCases = new Dictionary<int, List<ITestCase>>();
        var defaultPriorities = new Dictionary<Type, int>();

        foreach (var testCase in testCases)
        {
            var defaultPriority = DefaultPriorityForClass(testCase);
            var priority = PriorityForTest(testCase, defaultPriority);

            if (!groupedTestCases.ContainsKey(priority))
                groupedTestCases[priority] = new List<ITestCase>();

            groupedTestCases[priority].Add(testCase);
        }

        var orderedKeys = groupedTestCases.Keys.OrderBy(k => k);
        foreach (var list in orderedKeys.Select(priority => groupedTestCases[priority]))
        {
            list.Sort((x, y) => StringComparer.OrdinalIgnoreCase.Compare(x.TestMethod.Method.Name, y.TestMethod.Method.Name));
            foreach (TTestCase testCase in list)
                yield return testCase;
        }
    }

    private int PriorityForTest(ITestCase testCase, int defaultPriority)
    {
        var priorityAttribute = testCase.TestMethod.Method.GetCustomAttributes(_priorityAttributeName).SingleOrDefault();
        return priorityAttribute?.GetNamedArgument<int>(_priorityArgumentName) ?? defaultPriority;
    }

    private int DefaultPriorityForClass(ITestCase testCase)
    {
        var testClass = testCase.TestMethod.TestClass.Class;
        if (!_defaultPriorities.TryGetValue(testClass.Name, out var result))
        {
            var defaultAttribute = testClass.GetCustomAttributes(_defaultPriorityAttributeName).SingleOrDefault();
            result = defaultAttribute?.GetNamedArgument<int>(_priorityArgumentName) ?? int.MaxValue;
            _defaultPriorities[testClass.Name] = result;
        }

        return result;
    }
}

为了让测试按顺序运行,请将以下属性添加到需要的类中:

[TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)]
public abstract class TestsBase
{
    private FieldInfo _counterField;

    public TestsBase()
    {
        _counterField = this.GetType().GetField("_counter", BindingFlags.Static | BindingFlags.NonPublic);

        if (GetCounter() == null)
        {
            var facts = this.GetType().GetMethods()
                .Where(m => m.GetCustomAttribute<FactAttribute>() != null);
            SetCounter(new bool[facts.Count()]);
        }
    }

    private bool[] GetCounter() => _counterField.GetValue(this) as bool[];

    private void SetCounter(bool[] value) => _counterField.SetValue(this, value);

    protected void VerifyAndFlip(int testNumber)
    {
        var counter = GetCounter();
        counter.Take(testNumber).Should().AllBeEquivalentTo(true);
        counter.Skip(testNumber).Should().AllBeEquivalentTo(false);

        counter[testNumber] = true;
        SetCounter(counter);
    }
}

接下来,使用Priority属性装饰你的测试方法。

    [Fact, Priority(1)]
    public async Task Fact1_CorrectValue1_ReturnsTrue()
    {
       Assert.True(true);
    }

    [Fact, Priority(2)]
    public async Task Fact2_Value2_ReturnsTrue()
    {
        Assert.True(true);
    }

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