.NET & Xunit 设置优先级、顺序的单元测试

有时候我们期待以固定的顺序执行测试,比如先新增学生信息,再修改学生信息,再查询、再删除。在这种设计下,如果顺序发生变化,可能导致错误,比如修改一个不存在的学生信息,会导致测试不通过。
这里以Xunit为例,来看一下如何实现顺序执行单元测试。
直接谷歌xunit Priority unit test,可以得到一个官方的答案TestCaseOrdering
定义一个TestPriorityAttribute,用于获取unit tests的排序,实现ITestCaseOrderer接口,在OrderTestCases方法中通过TestPriorityAttribute进行排序,即可实现顺序执行unit tests。
TestPriorityAttribute

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class TestPriorityAttribute : Attribute
{
    public TestPriorityAttribute(int priority)
    {
        Priority = priority;
    }

    public int Priority { get; private set; }
}

PriorityOrderer

public class PriorityOrderer : ITestCaseOrderer
{
    public IEnumerable<TTestCase> OrderTestCases<TTestCase>(IEnumerable<TTestCase> testCases) where TTestCase : ITestCase
    {
        var sortedMethods = new SortedDictionary<int, List<TTestCase>>();

        foreach (TTestCase testCase in testCases)
        {
            int priority = 0;

            foreach (IAttributeInfo attr in testCase.TestMethod.Method.GetCustomAttributes((typeof(TestPriorityAttribute).AssemblyQualifiedName)))
                priority = attr.GetNamedArgument<int>("Priority");

            GetOrCreate(sortedMethods, priority).Add(testCase);
        }

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

    static TValue GetOrCreate<TKey, TValue>(IDictionary<TKey, TValue> dictionary, TKey key) where TValue : new()
    {
        TValue result;

        if (dictionary.TryGetValue(key, out result)) return result;

        result = new TValue();
        dictionary[key] = result;

        return result;
    }
}

这里写一些单元测试方法


[TestCaseOrderer("XUnit.Coverlet.Collector.TestPriority.PriorityOrderer", "XUnit.Coverlet.Collector")]
public class StudentServiceWithPriorityUnitTest
{
    static StudentService _studentService;
    static Student data = new Student()
    {
        Name = "test name"
    };

    static StudentServiceWithPriorityUnitTest()
    {
        _studentService = new StudentService();
    }

    [Fact, TestPriority(1)]
    public Student Insert_A_Student()
    {
        _studentService.Insert(data);
        data.ShouldNotBeNull();
        return data;
    }

    [Fact, TestPriority(2)]
    public void Update_A_Student()
    {
        data.Name = "update student name";
        _studentService.Update(data).ShouldBeTrue();
    }

    [Fact, TestPriority(3)]
    public void Update_A_Student_Failed()
    {
        var data = new Student()
        {
            Id = Guid.NewGuid(),
            Name = "test name"
        };
        Should.Throw<DataNotExistException>(() => _studentService.Update(data));
    }

    [Fact, TestPriority(4)]
    public void Get_A_Student()
    {
        var getData = _studentService.Get(data.Id);
        getData.ShouldNotBeNull();
        getData.Id.ShouldBeEquivalentTo(data.Id);
    }

    [Fact, TestPriority(5)]
    public void Get_A_Student_Failed()
    {
        Should.Throw<DataNotExistException>(() => _studentService.Get(Guid.NewGuid()));
    }

    [Fact, TestPriority(6)]
    public void Delete_A_Student()
    {
        _studentService.Delete(data.Id);
    }

    [Fact, TestPriority(7)]
    public void Delete_A_Student_Failed()
    {
        Should.Throw<DataNotExistException>(() => _studentService.Delete(Guid.NewGuid()));
    }
}

效果如下

调试模式下,也可以看到单元测试是按照顺序执行的。

需要注意的是,根据对单元测试排序,应该避免对单元测试排序。同时也提供了最佳做法
最佳做法我们将会在下篇博客中进行讨论。

示例代码

PriorityOrderer
TestPriorityAttribute
StudentServiceWithPriorityUnitTest

参考资料

TestCaseOrdering
使用 dotnet test 和 xUnit 在 .NET Core 中进行 C# 单元测试
对单元测试排序
.NET Core 和 .NET Standard 单元测试最佳做法

posted @ 2022-11-06 13:48  Lulus  阅读(736)  评论(0编辑  收藏  举报