Tạo DataAttribute tùy chỉnh cho Theory của xUnit để tải dữ liệu từ file JSON

Trong bài trước, tôi đã mô tả các cách khác nhau để truyền dữ liệu vào phương thức kiểm tra [Theory] của xUnit. Đó là:

  • [InlineData] - Truyền dữ liệu cho các tham số của phương thức kiểm tra [Theory].
  • [ClassData] - Tạo một lớp tùy chỉnh triển khai interface IEnumerable<object[]> và sử dụng lớp này để trả về dữ liệu thử nghiệm.
  • [MemberData] - Tạo một thuộc tính hoặc phương thức static trả về kiểu dữ liệu IEnumerable<object[]> và sử dụng nó để cung cấp dữ liệu thử nghiệm.
Tạo các bài kiểm tra unit test được tham số hóa trong xUnit với Theory, InlineData, ClassData và MemberData
Trong bài viết này, tôi sẽ hướng dẫn bạn tạo các bài kiểm tra unit test được tham số hóa trong xUnit với Theory, InlineData, ClassData và MemberData

Tất cả những thuộc tính trên đều xuất phát từ lớp cơ sở DataAttribute, đó là một phần của namespace xUnit SDK là: XUnit.Sdk.

Trong bài viết này, tôi sẽ hướng dẫn bạn cách tạo triển khai lớp DataAttribute của riêng mình. Điều này cho phép bạn tải dữ liệu từ bất kỳ nguồn nào bạn muốn. Để làm ví dụ, tôi sẽ hướng dẫn bạn cách tải dữ liệu từ tệp JSON.

Lớp cơ sở DataAttribute

Lớp cơ sở DataAttribute rất đơn giản. Đó là một lớp abstract kế thừa từ lớp Attribute, với một phương thức duy nhất để triển khai là GetData(), nó trả về dữ liệu thử nghiệm:

public abstract class DataAttribute : Attribute
{
    public virtual string Skip { get; set; }

    public abstract IEnumerable<object[]> GetData(MethodInfo testMethod);
}

Phương thức GetData() trả về kiểu dữ liệu kiểu IEnumerable<object[]>, nó sẽ rất quen thuộc nếu bạn đọc bài viết trước của tôi, hoặc bạn đã sử dụng một trong hai thuộc tính là [ClassData] hoặc [MemberData]. Mỗi phần tử object[] là một phần của IEnumerable<> chứa các tham số cho một lần chạy thử nghiệm [Theory].

Sử dụng thuộc tính JsonFileDataAttribute tùy chỉnh

Để triển khai một phương thức tùy chỉnh, bạn chỉ cần kế thừa từ ​​lớp này và triển khai phương thức GetData. Sau đó, bạn có thể sử dụng thuộc tính mới của mình để truyền dữ liệu sang phương thức kiểm tra [Theory].

Trong bài viết này, chúng ta sẽ tạo một thuộc tính tải dữ liệu từ tệp JSON, được gọi là JsonFileDataAttribute. Chúng ta có thể thêm điều này vào bài kiểm tra [Theory] và nó sẽ sử dụng tất cả dữ liệu trong tệp JSON làm dữ liệu cho các lần chạy kiểm tra:

[Theory]
[JsonFileData("all_data.json")]
public void CanAddAll(int value1, int value2, int expected)
{
    var calculator = new Calculator();

    var result = calculator.Add(value1, value2);

    Assert.Equal(expected, result);
}

Với cách sử dụng này, tệp cung cấp toàn bộ dữ liệu cho bài kiểm tra [Theory]. Ngoài ra, bạn có thể chỉ định một giá trị thuộc tính ngoài tên tệp. Điều này cho phép bạn có một tệp JSON duy nhất chứa dữ liệu cho nhiều bài kiểm tra [Theory], ví dụ:

[Theory]
[JsonFileData("data.json", "AddData")]
public void CanAdd(int value1, int value2, int expected)
{
    var calculator = new Calculator();

    var result = calculator.Add(value1, value2);

    Assert.Equal(expected, result);
}

[Theory]
[JsonFileData("data.json", "SubtractData")]
public void CanSubtract(int value1, int value2, int expected)
{
    var calculator = new Calculator();

    var result = calculator.Subtract(value1, value2);

    Assert.Equal(expected, result);
}

Đó là cách chúng ta sử dụng attribute, bây giờ chúng ta sẽ xem cách tạo ra nó.

Tạo lớp JsonFileDataAttribute tùy chỉnh

Việc triển khai lớp JsonFileDataAttribute sử dụng đường dẫn tệp để tải tệp JSON. Sau đó, nó sẽ deserialize tệp thành kiểu IEnumerable<object[]>.

Tôi đã không cố gắng tối ưu hóa điều này chút nào, vì vậy nó chỉ tải toàn bộ tệp vào bộ nhớ và sau đó deserialize. Bạn có thể làm được nhiều hơn về mặt đó nếu hiệu suất là một vấn đề.

public class JsonFileDataAttribute : DataAttribute
{
    private readonly string _filePath;
    private readonly string _propertyName;

    /// <summary>
    /// Load data from a JSON file as the data source for a theory
    /// </summary>
    /// <param name="filePath">The absolute or relative path to the JSON file to load</param>
    public JsonFileDataAttribute(string filePath)
        : this(filePath, null) { }

    /// <summary>
    /// Load data from a JSON file as the data source for a theory
    /// </summary>
    /// <param name="filePath">The absolute or relative path to the JSON file to load</param>
    /// <param name="propertyName">The name of the property on the JSON file that contains the data for the test</param>
    public JsonFileDataAttribute(string filePath, string propertyName)
    {
        _filePath = filePath;
        _propertyName = propertyName;
    }

    /// <inheritDoc />
    public override IEnumerable<object[]> GetData(MethodInfo testMethod)
    {
        if (testMethod == null) 
        { 
            throw new ArgumentNullException(nameof(testMethod)); 
        }

        // Get the absolute path to the JSON file
        var path = Path.IsPathRooted(_filePath)
            ? _filePath
            : Path.GetRelativePath(Directory.GetCurrentDirectory(), _filePath);

        if (!File.Exists(path))
        {
            throw new ArgumentException($"Could not find file at path: {path}");
        }

        // Load the file
        var fileData = File.ReadAllText(_filePath);

        if (string.IsNullOrEmpty(_propertyName))
        {
            //whole file is the data
            return JsonConvert.DeserializeObject<List<object[]>>(fileData);
        }

        // Only use the specified property as the data
        var allData = JObject.Parse(fileData);
        var data = allData[_propertyName];
        return data.ToObject<List<object[]>>();
    }
}

Lớp JsonFileDataAttribute hỗ trợ đường dẫn tập tin tương đối hoặc tuyệt đối, chỉ cần nhớ rằng đường dẫn tập tin sẽ được tương đối so với thư mục mà thử nghiệm của bạn thực hiện.

Bạn cũng có thể nhận thấy rằng phương thức GetData() được cung cấp một tham số MethodInfo. Nếu muốn, bạn có thể cập nhật lớp JsonFileDataAttribute để tự động sử dụng tên phương thức thử nghiệm Theory làm thuộc tính con JSON, nhưng tôi sẽ để đó như một bài tập!

Tải dữ liệu từ file JSON

Chỉ để hoàn thành bức tranh, giải pháp sau chứa hai tệp JSON, data.jsonall_data.json, cung cấp dữ liệu cho các thử nghiệm được hiển thị trước đó trong bài đăng này.

Tải dữ liệu từ file JSON

Bạn phải đảm bảo các tệp được sao chép vào đầu ra thử nghiệm, bạn có thể thực hiện điều này từ hộp thoại thuộc tính như được hiển thị ở trên hoặc bằng cách thiết lập thuộc tính CopyToOutputDirectory trong file .csproj của bạn:

<ItemGroup>
  <None Update="all_data.json" CopyToOutputDirectory="PreserveNewest" />
  <None Update="data.json" CopyToOutputDirectory="PreserveNewest" />
  <None Update="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

Tệp data.json chứa hai thuộc tính, dành cho hai bài kiểm tra Theory khác nhau. Lưu ý rằng mỗi thuộc tính là một mảng của mảng, vì vậy chúng ta có thể deserialize nó thành một đối tượng kiểu IEnumerable<object[]>.

{
  "AddData": [
    [ 1, 2, 3 ],
    [ -4, -6, -10 ],
    [ -2, 2, 0 ]
  ],
  "SubtractData": [
    [ 1, 2, -1 ],
    [ -4, -6, 2 ],
    [ 2, 2, 0 ]
  ]
}

Mặt khác, tệp all_data.json bao gồm một mảng mảng đơn lẻ, vì chúng tôi đang sử dụng toàn bộ tệp làm nguồn cho bài kiểm tra lý thuyết.

[
  [ 1, 2, 3 ],
  [ -4, -6, -10 ],
  [ -2, 2, 0 ]
]

Với mọi thứ đã có, chúng tôi có thể chạy tất cả các bài kiểm tra lý thuyết, sử dụng dữ liệu từ các tệp:

Tải dữ liệu từ file JSON

Tóm lược

xUnit chứa khái niệm về các bài kiểm tra được tham số hóa, vì vậy bạn có thể viết các bài kiểm tra bằng cách sử dụng một loạt dữ liệu. Bạn có thể sử dụng các lớp [InlineData], [ClassData][MemberData] để truyền dữ liệu cho thử nghiệm Theory. Tất cả các thuộc tính này đều bắt nguồn từ lớp DataAttribute, bạn cũng có thể kế thừa từ đó để tạo nguồn dữ liệu tùy chỉnh của riêng mình.

Trong bài đăng này, tôi đã chỉ ra cách bạn có thể tạo một DataAttribute tùy chỉnh là JsonFileDataAttribute để tải dữ liệu từ tệp JSON. Đây là cách triển khai cơ bản, nhưng bạn có thể dễ dàng mở rộng nó để đáp ứng nhu cầu của riêng mình.

Unit TestXUnit
Bài Viết Liên Quan:
E2E Test với ASP.NET Core, XUnit và Playwright
Trung Nguyen 15/10/2021
E2E Test với ASP.NET Core, XUnit và Playwright

Trong bài viết này, chúng ta sẽ tìm hiểu cách sử dụng thư viện Playwright kết hợp với XUnit để kiểm tra các ứng dụng web ASP.NET Core như người dùng có thể.

Tạo các bài kiểm tra unit test được tham số hóa trong xUnit với Theory, InlineData, ClassData và MemberData
Trung Nguyen 18/04/2021
Tạo các bài kiểm tra unit test được tham số hóa trong xUnit với Theory, InlineData, ClassData và MemberData

Trong bài viết này, tôi sẽ hướng dẫn bạn tạo các bài kiểm tra unit test được tham số hóa trong xUnit với Theory, InlineData, ClassData và MemberData

Sử dụng Theory và InlineData của xUnit để viết unit test
Trung Nguyen 18/04/2021
Sử dụng Theory và InlineData của xUnit để viết unit test

Trong bài này, chúng ta sẽ sử dụng thuộc tính [Theory] và [InlineData] của xUnit để nhanh chóng viết một loạt các bài kiểm tra unit test.

Unit test cho controller trong ASP.NET Core với xUnit và Moq
Trung Nguyen 18/04/2021
Unit test cho controller trong ASP.NET Core với xUnit và Moq

Trong bài viết này, chúng ta sẽ viết một số bài kiểm tra unit test cho Controller của ứng dụng ASP.NET sử dụng xUnit và Moq.