xUnit là một công cụ Unit Test miễn phí, mã nguồn mở, tập trung vào cộng đồng cho .NET Framework. Được viết bởi nhà phát minh ban đầu của NUnit v2, xUnit là công nghệ mới nhất để kiểm tra đơn vị C #, F #, VB.NET và các ngôn ngữ .NET khác. xUnit trở nên phổ biến hơn khi Microsoft bắt đầu sử dụng nó cho CoreFX và ASP.NET Core. Đây cũng là khung thử nghiệm tôi sử dụng trong hầu hết các dự án của mình. Vì vậy, bài đăng này là tổng quan về các khả năng của khuôn khổ này.

dotnet new xunit

Hoặc sử dụng Visual Studio

Csproj sẽ giống như sau:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
    <PackageReference Include="xunit" Version="2.4.1" />
    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
  </ItemGroup>
</Project>

Bây giờ bạn có thể tạo thử nghiệm đầu tiên của mình:

using Xunit;

public class UnitTest1
{
    [Fact]
    public void Test1()
    {
        var sum = 18 + 24;
        Assert.Equal(42, sum);
    }
}

Nếu mọi thứ ổn, bạn sẽ thấy kiểm thử màu xanh lá cây trong Test Explorer:

Bạn cũng có thể sử dụng dòng lệnh:

dotnet test

2. Tìm hiểu về khẳng định

Class duy nhất bạn cần biết là Xunit.Assert. Class này cho phép so sánh các giá trị, chuỗi, tập hợp, ngoại lệ và sự kiện. Dưới đây là một vài ví dụ:

// Any values
int value = 0;
Assert.Equal(42, value);
Assert.NotEqual(42, value);

// Boolean
bool b = true;
Assert.True(b, "b should be true");
Assert.False(b, "b should be false");

// Strings
var str = "";
Assert.Equal("", str, ignoreCase: false, ignoreLineEndingDifferences: false, ignoreWhiteSpaceDifferences: false);
Assert.StartsWith("prefix", str, StringComparison.Ordinal);
Assert.EndsWith("suffix", str, StringComparison.Ordinal);
Assert.Matches("[0-9]+", str);

// Collections
var collection = new [] { 1, 2, 3 };
Assert.Empty(collection);
Assert.NotEmpty(collection);
Assert.Single(collection); // Contains only 1 item
Assert.Single(collection, item => item == 1); // Contains only 1 item
Assert.Equal(new int[] { 1, 2, 3 }, collection);
Assert.Contains(0, collection);
Assert.Contains(collection, item => item == 1);

// Assert each items of the collection match the condition
Assert.All(collection, item => Assert.InRange(item, low: 0, high: 10));

// Assert the collection contains 3 items and the items match the conditions (in the declared order)
Assert.Collection(collection,
                item => Assert.Equal(1, item),
                item => Assert.Equal(2, item),
                item => Assert.Equal(3, item));

// Exceptions
var ex1 = Assert.Throws<Exception>(() => Console.WriteLine()); // Return the thrown exception
var ex2 = await Assert.ThrowsAsync<Exception>(() => Task.FromResult(1)); // Return the thrown exception
Assert.Equal("message", ex1.Message);

// Events
var test = new Test();
Assert.Raises<EventArgs>(
    handler => test.MyEvent += handler,
    handler => test.MyEvent -= handler,
    () => test.RaiseEvent());

3. Tạo các bài kiểm tra được tham số hóa trong xUnit

xUnit cho phép viết một bài kiểm tra và thực hành nó với nhiều dữ liệu bằng Theory. Bạn có thể đặt dữ liệu bằng thuộc tính, phương thức, lớp hoặc thuộc tính tùy chỉnh.

public class UnitTest1
{
    // You can mix multiple data sources
    // xUnit provides an analyzer to ensure the data sources are valid for the test
    [Theory]
    [InlineData(1, 2, 3)]              // InlineData works for constant values
    [MemberData(nameof(Test1Data))]    // MemberData can be a public static method or property
    [ClassData(typeof(TestDataClass))] // TestDataClass must implement IEnumerable<object[]>
    [CustomDataAttribute]              // Any attribute that inherits from DataAttribute
    public void Test1(int a, int b, int expected)
    {
        Assert.Equal(expected, a + b);
    }

    // The method can return IEnumerable<object[]>
    // You can use TheoryData to strongly type the result
    public static TheoryData<int, int, int> Test1Data()
    {
        var data = new TheoryData<int, int, int>();
        data.Add(18, 24, 42);
        data.Add(6, 7, 13);
        return data;
    }

    public class TestDataClass : IEnumerable<object[]>
    {
        public IEnumerator<object[]> GetEnumerator()
        {
            yield return new object[] { 9, 1, 10 };
            yield return new object[] { 9, 10, 19 };
        }

        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    }

    private class CustomDataAttribute : DataAttribute
    {
        public override IEnumerable<object[]> GetData(MethodInfo testMethod)
        {
            yield return new object[] { 2, 3, 4 };
        }
    }
}

4. Thiết lập / Chia nhỏ các bài kiểm tra

xUnit khởi tạo một phiên bản của class cho mỗi lần kiểm tra. Điều này có nghĩa là bạn có thể sử dụng hàm tạo để thiết lập các bài kiểm tra. xUnit gọi Dispose sau mỗi lần kiểm tra nếu class triển khai IDisposable. Khi xUnit tạo một phiên bản cho mỗi lần kiểm tra, trạng thái không được chia sẻ giữa các lần kiểm tra.

// The class is instantiated once per test
// This means the constructor/Dispose are called once per test
public class UnitTest1 : IDisposable, IAsyncLifetime
{
    public UnitTest1()
    {
        // Set up (called once per test)
    }

    public void Dispose()
    {
        // Tear down (called once per test)
    }

    public Task InitializeAsync()
    {
        // Async set up (called once per test)
    }

    public Task DisposeAsync()
    {
        // Async Tear down (called once per test)
    }

    [Fact]
    public void Test1()
    {
        // Test
    }

    [Fact]
    public void Test2()
    {
        // Test
    }
}

5. Chia sẻ dữ liệu giữa các bài kiểm tra

xUnit cung cấp một cách để suy nghĩ về dữ liệu mỗi bài kiểm tra bằng việc sử dụng giao diện IClassFixture và ICollectionFixture. xUnit sẽ tạo một phiên bản duy nhất của dữ liệu cố định và chuyển nó đến phương thức khởi tạo của bạn trước khi chạy mỗi thử nghiệm. Tất cả các bài kiểm tra đều chia sẻ cùng một trường hợp dữ liệu cố định. Sau khi tất cả các bài kiểm tra đã chạy, người chạy sẽ loại bỏ dữ liệu cố định, nếu nó triển khai IDisposable.

public class MyFixture : IDisposable, IAsyncLifetime
{
    public MyFixture()
    {
        // Called once before running all tests in UnitTest1
    }

    public void Dispose()
    {
        // Called once after running all tests in UnitTest1
    }

    public Task InitializeAsync()
    {
        // Called once before running all tests in UnitTest1
    }

    public Task DisposeAsync()
    {
        // Called once after running all tests in UnitTest1
    }
}

public class UnitTest1 : IClassFixture<MyFixture>
{
    private readonly MyFixture _fixture;

    // All the tests share the same instance of MyFixture
    public UnitTest1(MyFixture fixture)
    {
        _fixture = fixture;
    }

    [Fact]
    public void Test1()
    {
    }

    [Fact]
    public void Test2()
    {
    }
}

Bạn có thể chia sẻ dữ liệu giữa các class bằng cách sử dụng khái niệm tập hợp.

public class MyFixture : IDisposable, IAsyncLifetime
{
    public MyFixture()
    {
        // Called once before running all tests in the collection #1
    }

    public void Dispose()
    {
        // Called once after running all tests in the collection #1
    }

    public Task InitializeAsync()
    {
        // Called once before running all tests in the collection #1
    }

    public Task DisposeAsync()
    {
        // Called once after running all tests in the collection #1
    }
}

// Define the collection with the fixture
[CollectionDefinition("Collection #1")]
public class Collection1Class : ICollectionFixture<MyFixture> { }

// Declare test in the defined collection
[Collection("Collection #1")]
public class UnitTest1
{
    private readonly MyFixture _fixture;

    public UnitTest1(MyFixture fixture)
    {
        _fixture = fixture;
    }

    [Fact]
    public void Test1()
    {
    }
}

[Collection("Collection #1")]
public class UnitTest2
{
    private readonly MyFixture _fixture;

    public UnitTest2(MyFixture fixture)
    {
        _fixture = fixture;
    }

    [Fact]
    public void Test2()
    {
    }
}

6. Thực hiện các bài kiểm tra song song

Theo mặc định, xUnit chạy bộ sưu tập thử nghiệm song song và tạo 1 bộ sưu tập thử nghiệm cho mỗi class.

// Test1 and Test2 runs sequentially => Runs in 10s
// Test3 and Test4 runs sequentially => Runs in 10s
// (Test1, Test2) runs in parallel with (Test3, Test4) => Runs in 10s
public class UnitTest1
{
    [Fact]
    public async Task Test1() => await Task.Delay(5000);

    [Fact]
    public async Task Test2() => await Task.Delay(5000);
}

public class UnitTest2
{
    [Fact]
    public void Test3() => Thread.Sleep(5000);

    [Fact]
    public void Test4() => Thread.Sleep(5000);
}

Bạn có thể tạo bộ sưu tập của riêng mình để nhóm các bài kiểm tra bằng cách sử dụng thuộc tính [Collection(“name”)]:

// Test1 Test2, Test3, Test4 run sequentially because there are in the same collection
// => Runs in 20s
[Collection("Collection #1")]
public class UnitTest1
{
    [Fact]
    public async Task Test1() => await Task.Delay(5000);

    [Fact]
    public async Task Test2() => await Task.Delay(5000);
}

[Collection("Collection #1")]
public class UnitTest2
{
    [Fact]
    public void Test3() => Thread.Sleep(5000);

    [Fact]
    public void Test4() => Thread.Sleep(5000);
}

Bạn có thể kiểm soát việc song song hóa bằng cách sử dụng CollectionBehaviorAttribute:

[assembly: Xunit.CollectionBehaviorAttribute(
                    CollectionBehavior.CollectionPerAssembly,
                    DisableTestParallelization = false,
                    MaxParallelThreads = 4)]

Bạn có thể tắt chế độ song song cho Bộ sưu tập thử nghiệm cụ thể. Các bộ sưu tập thử nghiệm có khả năng song song sẽ được chạy đầu tiên (song song), sau đó là các bộ sưu tập thử nghiệm bị vô hiệu hóa song song (chạy tuần tự).

[CollectionDefinition("Collection #1", DisableParallelization = true)]
public class Collection1Class { }

[Collection("Collection #1")]
public class UnitTest1
{
    [Fact]
    public void Test1()
    {
    }
}

7. Phân loại các bài kiểm tra / Chạy một tập hợp con các bài kiểm tra

Các đặc điểm cho phép thêm siêu dữ liệu bổ sung vào một bài kiểm tra. Nó giống như một danh sách các khóa / giá trị được liên kết với một bài kiểm tra. Bạn có thể đặt đặc điểm ở cấp lớp hoặc cấp độ kiểm tra:

[Trait("Area", "API")]
public class UnitTest1
{
    [Fact]
    [Trait("Category", "Integration")]
    [Trait("Issue", "123")]
    public void Test1()
    {
    }
}

Bạn có thể xem danh sách các đặc điểm trong Visual Studio Test Explorer:

Bạn nên tạo các hằng số để xác định các đặc điểm trong dự án của mình:

internal static class Traits
{
    public const string Category = "Category";

    public static class Categories
    {
        public const string Integration = "Integration";
    }
}

public class UnitTest1
{
    [Fact]
    [Trait(Traits.Category, Traits.Categories.Integration)]
    public void Test1()
    {
    }
}

Nếu không muốn lặp lại chính mình, bạn cũng có thể tạo thuộc tính của mình để xác định các đặc điểm:

[TraitDiscoverer("Sample.FunctionalTestDiscoverer", "XUnitTestProject1")]
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public sealed class FunctionalTestAttribute : Attribute, ITraitAttribute
{
}

public sealed class FunctionalTestDiscoverer : ITraitDiscoverer
{
    public IEnumerable<KeyValuePair<string, string>> GetTraits(IAttributeInfo traitAttribute)
    {
        yield return new KeyValuePair<string, string>("Category", "Integration");
    }
}

public class UnitTest1
{
    [Fact]
    [FunctionalTest] // Equivalent of [Trait("Category", "Integration")]
    public void Test1()
    {
    }
}

Phần thú vị nhất của việc xác định các đặc điểm là nó cho phép bạn chỉ chạy một tập hợp con của các bài kiểm tra:

dotnet test –filter “Area=API”
dotnet test –filter “Area=API&Category!=Integration”

8. Ghi lại đầu ra thử nghiệm

Nếu bạn muốn ghi lại một số dữ liệu để giúp debug kiểm tra của mình, bạn có thể sử dụng giao diện ITestOutputHelper. Bạn nên ghi lại bất kỳ thứ gì hữu ích để chẩn đoán các bài kiểm tra không đạt, đặc biệt nếu bài kiểm tra chạy trên một máy từ xa chẳng hạn như CI machine.

public class UnitTest1
{
    private readonly ITestOutputHelper _output;

    public UnitTest1(ITestOutputHelper testOutputHelper)
    {
        _output = testOutputHelper;
    }

    [Fact]
    public void Test1()
    {
        _output.WriteLine("This is a log from the test output helper");
    }
}

Trong trường hợp kiểm tra không thành công, đầu ra sẽ được xuất ra bảng điều khiển.

9. Bỏ qua các bài kiểm tra

Đôi khi bạn cần phải bỏ qua một số bài kiểm tra. Ví dụ: nếu bạn có một bài kiểm tra không ổn định, bạn có thể vô hiệu hóa nó cho đến khi bạn khắc phục được.

[Fact(Skip = "This test is disabled")]
public void Test1()
{
}

Tuy nhiên, không có cách nào để tự động bỏ qua một bài kiểm tra. Những gì bạn có thể làm là tạo một thuộc tính tùy chỉnh:

public class UnitTest1
{
    [IgnoreOnWindowsFactAttribute]
    public void Test1()
    {
    }
}

public sealed class IgnoreOnWindowsFactAttribute : FactAttribute
{
    public IgnoreOnWindowsFactAttribute()
    {
        if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
        {
            Skip = "Ignored on Windows";
        }
    }
}

10. Thử nghiệm dựa trên nhiều framework

Bạn có thể chạy thử nghiệm với nhiều framework bằng cách nhắm mục tiêu nhiều dự án thử nghiệm:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFrameworks>net461;netcoreapp3.1;net5.0</TargetFrameworks>
  </PropertyGroup>

  ...

</Project>

Sử dụng dotnet test sẽ chạy thử nghiệm trên từng framework mục tiêu hoặc bạn có thể chỉ định một framework:

dotnet test
dotnet test –framework net461

11. Thay đổi tên hiển thị của các bài kiểm tra

Theo mặc định, xUnit sử dụng tên của phương thức làm tên hiển thị. Bạn có thể thay đổi hành vi này bằng cách sử dụng thuộc tính DisplayName:

[Fact(DisplayName = "1 + 1 = 2")]
public void Test_that_1_plus_1_eq_2()
{
    Assert.Equal(2, 1 + 1);
}

Bạn cũng có thể tạo một tệp có tên xunit.runner.json tại thư mục gốc của dự án thử nghiệm và đặt các tùy chọn methodDisplay:

{
  "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
  "methodDisplay": "method",
  "methodDisplayOptions": "replaceUnderscoreWithSpace,useOperatorMonikers,useEscapeSequences,replacePeriodWithComma"
}

Sử dụng tệp này, tên phương pháp sau Test_that_1_X2B_1_eq_3_U263A được hiển thị dưới dạng Kiểm tra rằng 1 + 1 = 3 ☺.

12. Thứ tự thực hiện các bộ sưu tập thử nghiệm / trường hợp thử nghiệm

Theo mặc định, xUnit không sắp xếp thứ tự các bộ sưu tập và việc thực thi các trường hợp thử nghiệm. Điều này có nghĩa là chúng sẽ chạy theo thứ tự ngẫu nhiên. Nếu bạn muốn thực thi chúng theo một thứ tự cụ thể, bạn có thể tạo một lớp triển khai ITestCollectionOrderer và ITestCaseOrderer để tùy chỉnh thứ tự thực thi.

[assembly: TestCollectionOrderer("Sample.CustomTestCollectionOrderer", "XUnitTestProject1")]
[assembly: TestCaseOrderer("Sample.CustomTestCaseOrderer", "XUnitTestProject1")]

namespace Sample
{
    public class CustomTestCollectionOrderer : ITestCollectionOrderer
    {
        public IEnumerable<ITestCollection> OrderTestCollections(IEnumerable<ITestCollection> testCollections)
        {
            return testCollections.OrderByDescending(collection => collection.DisplayName);
        }
    }

    public class CustomTestCaseOrderer : ITestCaseOrderer
    {
        public IEnumerable<TTestCase> OrderTestCases<TTestCase>(IEnumerable<TTestCase> testCases)
            where TTestCase : ITestCase
        {
            return testCases.OrderByDescending(test => test.DisplayName);
        }
    }

    public class UnitTest1
    {
        [Fact]
        public void Test2() => Thread.Sleep(5000);

        [Fact]
        public void Test1() => Thread.Sleep(5000);
    }

    // The attribute can also be set on the test collection
    [TestCaseOrderer("Sample.CustomTestCaseOrderer", "XUnitTestProject1")]
    public class UnitTest2
    {
        [Fact]
        public void Test3() => Thread.Sleep(5000);
        [Fact]
        public void Test4() => Thread.Sleep(5000);
    }
}

13. Thông tin thêm

xUnit có nhiều điểm tích hợp hơn có thể cần cho các kịch bản nâng cao. Hãy đọc tài liệu xUnit rất đầy đủ để biết thêm thông tin: https://xunit.net!

Đọc bài viết Tiếng Anh: https://www.meziantou.net/quick-introduction-to-xunitdotnet.htm#capturing-test-outpu

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *

Copy link
Powered by Social Snap