自定義xunit測試用例的執行順序

2023-11-06 12:01:16

有的時候我們會對程式進行單元測試, 為了測試的效果以及後期的維護, 我一般會將各個測試拆開, 根據需要測試的類分到各個型別中, 不過在實際操作的時候就出現了一些意想不到的問題, 各個測試的執行是亂序的, 按照我自己寫測試的習慣, 假如我需要測試新寫的增刪改查的功能, 我會將增刪改查分開測試, 會按照 新增-->查詢-->修改-->刪除 這樣的順序編寫, 在我的預想中新增操作應當是最先執行的, 但是在實際的執行過程中完全不是這樣, 隨機的順序對這樣的測試會有巨大的影響

所以就需要控制測試的執行順序

為測試用例排序

一般而言, 我們會將增刪改查全都放在一個測試集中

public class UserInfoTest
{
    [Fact]
    public void AddTest()
    {
    }
    [Fact]
    public void ReadTest()
    {
    }
    [Fact]
    public void UpdateTest()
    {
    }
    [Fact]
    public void DeleteTest()
    {
    }
}

上面就是增刪查改的測試用例, 儘管在寫的時候看起來是按照我所希望的那樣進行排序的, 但是在實際的執行過程卻有可能是完全亂序的, 而我需要他們按照上面的順序執行

實現ITestCaseOrderer

Xunit 中有一個 TestCaseOrdererAttribute, 加在測試類上時可以對測試類中包含的測試用例進行排序

但若是需要讓 TestCaseOrdererAttribute 起效, 我們還需要實現一個介面 ITestCaseOrderer

public class TestOrders : ITestCaseOrderer
{
    public IEnumerable<TTestCase> OrderTestCases<TTestCase>(IEnumerable<TTestCase> testCases) where TTestCase : ITestCase
    {
        var result = testCases.ToList();
        return result;
    }
}

通過實現 ITestCaseOrderer 我們可以獲取到測試用例, 之後只需要將測試用例重新進行排序, 排序成預期中需要的順序即可

最簡單的就是根據名字來進行排序, 此時只需要使用 result.OrderBy(item => item.DisplayName) 就差不多可以了

但是為了嚴謹以及後續的可維護性, 最好在做一個新的 Attribute 對需要排序的測試用例進行標記

新建OrderAttribute

[AttributeUsage(AttributeTargets.Method)]
public class OrderAttribute : Attribute
{
    public int Sort { get; set; }
    public OrderAttribute(int sort)
    {
        this.Sort = sort;
    }
}

OrderAttribute 的構成非常簡單, 其中只包含一個 Sort 用來進行排序

完善TestOrders的實現

public class TestOrders : ITestCaseOrderer
{
    public IEnumerable<TTestCase> OrderTestCases<TTestCase>(IEnumerable<TTestCase> testCases) where TTestCase : ITestCase
    {
        var typeName = typeof(OrderAttribute).AssemblyQualifiedName;
        var result = testCases.ToList();
        result.Sort((x, y) =>
        {
            var xOrder = x.TestMethod.Method.GetCustomAttributes(typeName)?.FirstOrDefault();
            if (xOrder == null)
            {
                return 0;
            }
            var yOrder = y.TestMethod.Method.GetCustomAttributes(typeName)?.FirstOrDefault();
            if (yOrder == null)
            {
                return 0;
            }
            var sortX = xOrder.GetNamedArgument<int>("Sort");
            var sortY = yOrder.GetNamedArgument<int>("Sort");
            return sortX - sortY;
        });
        return result;
    }
}

完善 TestOrders 的實現, 使得測試用例可以按照順序進行排序

測試用例打標

[TestCaseOrderer("TestOrders這個型別所在的namespace.TestOrders", "TestOrders這個型別所在的namespace")]
public class UserInfoTest
{
    [Fact, Order(0)]
    public void AddTest()
    {
    }
    [Fact, Order(1)]
    public void ReadTest()
    {
    }
    [Fact, Order(2)]
    public void UpdateTest()
    {
    }
    [Fact, Order(3)]
    public void DeleteTest()
    {
    }
}

TestCaseOrdererAttribute 接收兩個引數, 一個是可以用來確定之前實現的 TestOrders 的完整名稱空間, 第二個是 TestOrders 所在的名稱空間

這兩個引數主要的功能就是確定 TestOrders 的位置, Xunit會根據這兩個引數找到 TestOrders 並且呼叫排序的方法

然後在需要進行排序的測試用例上使用 [Order] 打標, 傳入自定義的排序, 然後在我們使用 dotnet test 就會按照傳入的排序執行測試用例了