Chuyển các tham số phức tạp cho [Lý thuyết]


98

Xunit có một tính năng hay : bạn có thể tạo một bài kiểm tra với một Theorythuộc tính và đưa dữ liệu vào InlineDatacác thuộc tính, và xUnit sẽ tạo nhiều bài kiểm tra và kiểm tra tất cả chúng.

Tôi muốn có một cái gì đó như thế này, nhưng các thông số để phương pháp của tôi không phải là 'dữ liệu đơn giản' (như string, int, double), nhưng một danh sách các lớp học của tôi:

public static void WriteReportsToMemoryStream(
    IEnumerable<MyCustomClass> listReport,
    MemoryStream ms,
    StreamWriter writer) { ... }

3
Nếu nó có ý nghĩa trong môi trường của bạn, bạn có thể làm điều đó trong F # với rất nhiều ít tiếng ồn: - stackoverflow.com/a/35127997/11635
Ruben Bartelink

1
Hướng dẫn đầy đủ gửi các đối tượng phức tạp làm tham số cho Phương pháp kiểm tra các loại phức tạp trong Unit test
Iman Bahrampour

Câu trả lời:


137

Có nhiều xxxxDatathuộc tính trong XUnit. Kiểm tra ví dụ về PropertyDatathuộc tính.

Bạn có thể triển khai một thuộc tính trả về IEnumerable<object[]>. Mỗi object[]mà phương thức này tạo ra sau đó sẽ được "giải nén" dưới dạng tham số cho một lần gọi [Theory]phương thức của bạn .

Một tùy chọn khác là ClassData, hoạt động giống nhau, nhưng cho phép dễ dàng chia sẻ 'trình tạo' giữa các thử nghiệm trong các lớp / không gian tên khác nhau, đồng thời cũng tách 'trình tạo dữ liệu' khỏi các phương pháp thử nghiệm thực tế.

Hãy xem những ví dụ này từ đây :

Ví dụ về PropertyData

public class StringTests2
{
    [Theory, PropertyData(nameof(SplitCountData))]
    public void SplitCount(string input, int expectedCount)
    {
        var actualCount = input.Split(' ').Count();
        Assert.Equal(expectedCount, actualCount);
    }

    public static IEnumerable<object[]> SplitCountData
    {
        get
        {
            // Or this could read from a file. :)
            return new[]
            {
                new object[] { "xUnit", 1 },
                new object[] { "is fun", 2 },
                new object[] { "to test with", 3 }
            };
        }
    }
}

Ví dụ về ClassData

public class StringTests3
{
    [Theory, ClassData(typeof(IndexOfData))]
    public void IndexOf(string input, char letter, int expected)
    {
        var actual = input.IndexOf(letter);
        Assert.Equal(expected, actual);
    }
}

public class IndexOfData : IEnumerable<object[]>
{
    private readonly List<object[]> _data = new List<object[]>
    {
        new object[] { "hello world", 'w', 6 },
        new object[] { "goodnight moon", 'w', -1 }
    };

    public IEnumerator<object[]> GetEnumerator()
    { return _data.GetEnumerator(); }

    IEnumerator IEnumerable.GetEnumerator()
    { return GetEnumerator(); }
}

@dcastro: yeah, tôi thực sự tìm kiếm một số tài liệu trên xUnit gốc
Quetzalcoatl

2
@Nick: Tôi đồng ý đó là tương tự như PropertyData, nhưng cũng có thể, bạn đã chỉ ra lý do cho nó: static. Đó chính xác là lý do tại sao tôi không làm. ClassData là khi bạn muốn thoát khỏi tĩnh. Bằng cách đó, bạn có thể tái sử dụng (tức là lồng) các máy phát điện dễ dàng hơn.
quetzalcoatl

1
Bất kỳ ý tưởng điều gì đã xảy ra với ClassData? Tôi có thể tìm thấy nó trong xUnit2.0, hiện tại, tôi đang sử dụng MemberData với phương thức static, tạo ra phiên bản mới của lớp và trả về nó.
Erti-Chris Eelmaa

14
@Erti, dùng [MemberData("{static member}", MemberType = typeof(MyClass))]để thay thế ClassDatathuộc tính.
Junle Li

6
Kể từ C # 6, bạn nên sử dụng nameoftừ khóa thay vì mã hóa cứng tên thuộc tính (ngắt dễ dàng nhưng im lặng).
sara,

40

Để cập nhật câu trả lời của @ Quetzalcoatl: Thuộc tính [PropertyData]đã được thay thế bằng cách [MemberData]lấy đối số là tên chuỗi của bất kỳ phương thức tĩnh, trường hoặc thuộc tính nào trả về một IEnumerable<object[]>. (Tôi thấy đặc biệt tuyệt vời khi có một phương thức trình vòng lặp có thể thực sự tính toán các trường hợp thử nghiệm tại một thời điểm, tạo ra chúng khi chúng được tính toán.)

Mỗi phần tử trong chuỗi được trả về bởi điều tra viên là một object[]và mỗi mảng phải có cùng độ dài và độ dài đó phải là số đối số cho trường hợp thử nghiệm của bạn (được chú thích bằng thuộc tính [MemberData]và mỗi phần tử phải có cùng kiểu với tham số phương thức tương ứng . (Hoặc có thể chúng có thể là loại chuyển đổi, tôi không biết.)

(Xem ghi chú phát hành cho xUnit.net tháng 3 năm 2014bản vá thực tế với mã ví dụ .)


2
@davidbak Bộ mã hóa đã biến mất. Liên kết không hoạt động
Kishan Vaishnav

11

Tạo mảng đối tượng ẩn danh không phải là cách dễ nhất để xây dựng dữ liệu vì vậy tôi đã sử dụng mẫu này trong dự án của mình

Đầu tiên xác định một số lớp chia sẻ, có thể tái sử dụng

//http://stackoverflow.com/questions/22093843
public interface ITheoryDatum
{
    object[] ToParameterArray();
}

public abstract class TheoryDatum : ITheoryDatum
{
    public abstract object[] ToParameterArray();

    public static ITheoryDatum Factory<TSystemUnderTest, TExpectedOutput>(TSystemUnderTest sut, TExpectedOutput expectedOutput, string description)
    {
        var datum= new TheoryDatum<TSystemUnderTest, TExpectedOutput>();
        datum.SystemUnderTest = sut;
        datum.Description = description;
        datum.ExpectedOutput = expectedOutput;
        return datum;
    }
}

public class TheoryDatum<TSystemUnderTest, TExecptedOutput> : TheoryDatum
{
    public TSystemUnderTest SystemUnderTest { get; set; }

    public string Description { get; set; }

    public TExpectedOutput ExpectedOutput { get; set; }

    public override object[] ToParameterArray()
    {
        var output = new object[3];
        output[0] = SystemUnderTest;
        output[1] = ExpectedOutput;
        output[2] = Description;
        return output;
    }

}

Giờ đây, dữ liệu thử nghiệm cá nhân và dữ liệu thành viên của bạn dễ viết và sạch hơn ...

public class IngredientTests : TestBase
{
    [Theory]
    [MemberData(nameof(IsValidData))]
    public void IsValid(Ingredient ingredient, bool expectedResult, string testDescription)
    {
        Assert.True(ingredient.IsValid == expectedResult, testDescription);
    }

    public static IEnumerable<object[]> IsValidData
    {
        get
        {
            var food = new Food();
            var quantity = new Quantity();
            var data= new List<ITheoryDatum>();

            data.Add(TheoryDatum.Factory(new Ingredient { Food = food }                       , false, "Quantity missing"));
            data.Add(TheoryDatum.Factory(new Ingredient { Quantity = quantity }               , false, "Food missing"));
            data.Add(TheoryDatum.Factory(new Ingredient { Quantity = quantity, Food = food }  , true,  "Valid"));

            return data.ConvertAll(d => d.ToParameterArray());
        }
    }
}

DescriptionThuộc tính chuỗi là tự ném cho bạn một khúc xương khi một trong nhiều trường hợp thử nghiệm của bạn không thành công


1
Tôi thích điều này; nó có một số tiềm năng thực sự cho một đối tượng rất phức tạp mà tôi phải xác thực các xác nhận trên 90+ thuộc tính. Tôi có thể truyền vào một đối tượng JSON đơn giản, giải mã hóa nó và tạo dữ liệu cho một lần lặp thử nghiệm. Làm tốt lắm.
Gustyn

1
không phải các tham số cho IsValid Testmethod bị trộn lẫn - nó phải là IsValid (PCBiant, expitedResult, testDescription)?
pastacool

9

Giả sử rằng chúng ta có một lớp Xe phức tạp có lớp Nhà sản xuất:

public class Car
{
     public int Id { get; set; }
     public long Price { get; set; }
     public Manufacturer Manufacturer { get; set; }
}
public class Manufacturer
{
    public string Name { get; set; }
    public string Country { get; set; }
}

Chúng ta sẽ điền và vượt qua lớp Ô tô để làm bài kiểm tra Lý thuyết.

Vì vậy, hãy tạo một lớp 'CarClassData' trả về một thể hiện của lớp Xe như bên dưới:

public class CarClassData : IEnumerable<object[]>
    {
        public IEnumerator<object[]> GetEnumerator()
        {
            yield return new object[] {
                new Car
                {
                  Id=1,
                  Price=36000000,
                  Manufacturer = new Manufacturer
                  {
                    Country="country",
                    Name="name"
                  }
                }
            };
        }
        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    }

Đã đến lúc tạo một phương pháp kiểm tra (CarTest) và xác định chiếc xe như một tham số:

[Theory]
[ClassData(typeof(CarClassData))]
public void CarTest(Car car)
{
     var output = car;
     var result = _myRepository.BuyCar(car);
}

loại phức tạp trong lý thuyết

Chúc may mắn


3
Câu trả lời này giải quyết rõ ràng câu hỏi về việc chuyển một loại tùy chỉnh làm đầu vào Lý thuyết dường như bị thiếu trong câu trả lời đã chọn.
JD Cain

1
Đây chính xác là trường hợp sử dụng mà tôi đang tìm kiếm, đó là cách truyền một kiểu phức tạp làm tham số cho Lý thuyết. Hoạt động hoàn hảo! Điều này thực sự có lợi cho việc thử nghiệm các mẫu MVP. Bây giờ tôi có thể thiết lập nhiều trường hợp khác nhau của Chế độ xem ở tất cả các loại trạng thái và chuyển tất cả chúng vào cùng một Lý thuyết để kiểm tra tác động mà các phương pháp của Người trình bày có trên chế độ xem đó. Yêu nó!
Denis M. Kitchen

3

Bạn có thể thử cách này:

public class TestClass {

    bool isSaturday(DateTime dt)
    {
       string day = dt.DayOfWeek.ToString();
       return (day == "Saturday");
    }

    [Theory]
    [MemberData("IsSaturdayIndex", MemberType = typeof(TestCase))]
    public void test(int i)
    {
       // parse test case
       var input = TestCase.IsSaturdayTestCase[i];
       DateTime dt = (DateTime)input[0];
       bool expected = (bool)input[1];

       // test
       bool result = isSaturday(dt);
       result.Should().Be(expected);
    }   
}

Tạo một lớp khác để giữ dữ liệu thử nghiệm:

public class TestCase
{
   public static readonly List<object[]> IsSaturdayTestCase = new List<object[]>
   {
      new object[]{new DateTime(2016,1,23),true},
      new object[]{new DateTime(2016,1,24),false}
   };

   public static IEnumerable<object[]> IsSaturdayIndex
   {
      get
      {
         List<object[]> tmp = new List<object[]>();
            for (int i = 0; i < IsSaturdayTestCase.Count; i++)
                tmp.Add(new object[] { i });
         return tmp;
      }
   }
}

1

Đối với nhu cầu của tôi, tôi chỉ muốn chạy một loạt 'người dùng thử nghiệm' thông qua một số thử nghiệm - nhưng [ClassData], v.v. dường như quá mức cần thiết cho những gì tôi cần (vì danh sách các mục đã được bản địa hóa cho mỗi thử nghiệm).

Vì vậy, tôi đã làm như sau, với một mảng bên trong thử nghiệm - được lập chỉ mục từ bên ngoài:

[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
public async Task Account_ExistingUser_CorrectPassword(int userIndex)
{
    // DIFFERENT INPUT DATA (static fake users on class)
    var user = new[]
    {
        EXISTING_USER_NO_MAPPING,
        EXISTING_USER_MAPPING_TO_DIFFERENT_EXISTING_USER,
        EXISTING_USER_MAPPING_TO_SAME_USER,
        NEW_USER

    } [userIndex];

    var response = await Analyze(new CreateOrLoginMsgIn
    {
        Username = user.Username,
        Password = user.Password
    });

    // expected result (using ExpectedObjects)
    new CreateOrLoginResult
    {
        AccessGrantedTo = user.Username

    }.ToExpectedObject().ShouldEqual(response);
}

Điều này đã đạt được mục tiêu của tôi, đồng thời giữ cho mục đích của thử nghiệm rõ ràng. Bạn chỉ cần giữ cho các chỉ mục được đồng bộ hóa nhưng chỉ có vậy.

Kết quả có vẻ đẹp, nó có thể thu gọn và bạn có thể chạy lại một phiên bản cụ thể nếu gặp lỗi:

nhập mô tả hình ảnh ở đây


"Kết quả có vẻ đẹp, nó có thể thu gọn và bạn có thể chạy lại một phiên bản cụ thể nếu gặp lỗi". Điểm rất tốt. Một nhược điểm chính của MemberDatadường như là bạn không thể nhìn thấy cũng như chạy thử nghiệm với một đầu vào thử nghiệm cụ thể. Thật tệ.
Oliver Pearmain

Trên thực tế, tôi vừa phát hiện ra rằng có thể MemberDatanếu bạn sử dụng TheoryDatavà tùy ý IXunitSerializable. Thông tin thêm và biểu đồ ở đây ... github.com/xunit/xunit/issues/429#issuecomment-108187109
Oliver Pearmain

1

Đây là cách tôi giải quyết vấn đề của bạn, tôi đã có cùng một kịch bản. Vì vậy, nội dòng với các đối tượng tùy chỉnh và một số đối tượng khác nhau trên mỗi lần chạy.

    [Theory]
    [ClassData(typeof(DeviceTelemetryTestData))]
    public async Task ProcessDeviceTelemetries_TypicalDeserialization_NoErrorAsync(params DeviceTelemetry[] expected)
    {
        // Arrange
        var timeStamp = DateTimeOffset.UtcNow;

        mockInflux.Setup(x => x.ExportTelemetryToDb(It.IsAny<List<DeviceTelemetry>>())).ReturnsAsync("Success");

        // Act
        var actual = await MessageProcessingTelemetry.ProcessTelemetry(JsonConvert.SerializeObject(expected), mockInflux.Object);

        // Assert
        mockInflux.Verify(x => x.ExportTelemetryToDb(It.IsAny<List<DeviceTelemetry>>()), Times.Once);
        Assert.Equal("Success", actual);
    }

Vì vậy, đây là bài kiểm tra đơn vị của tôi, hãy để ý tham số params . Điều này cho phép gửi một số lượng đối tượng khác nhau. Và bây giờ là lớp DeviceTelemetryTestData của tôi :

    public class DeviceTelemetryTestData : IEnumerable<object[]>
    {
        public IEnumerator<object[]> GetEnumerator()
        {
            yield return new object[] { new DeviceTelemetry { DeviceId = "asd" }, new DeviceTelemetry { DeviceId = "qwe" } };
            yield return new object[] { new DeviceTelemetry { DeviceId = "asd" }, new DeviceTelemetry { DeviceId = "qwe" } };
            yield return new object[] { new DeviceTelemetry { DeviceId = "asd" }, new DeviceTelemetry { DeviceId = "qwe" } };
            yield return new object[] { new DeviceTelemetry { DeviceId = "asd" }, new DeviceTelemetry { DeviceId = "qwe" } };
        }

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

Hy vọng nó giúp !


-1

Tôi đoán bạn nhầm ở đây. TheoryThuộc tính xUnit thực sự có nghĩa là gì: Bạn muốn kiểm tra hàm này bằng cách gửi các giá trị đặc biệt / ngẫu nhiên dưới dạng các tham số mà hàm dưới kiểm tra này nhận được. Điều đó có nghĩa rằng những gì bạn định nghĩa như là thuộc tính tiếp theo, chẳng hạn như: InlineData, PropertyData, ClassData, vv .. sẽ là nguồn cho những tham số. Điều đó có nghĩa là bạn nên xây dựng đối tượng nguồn để cung cấp các tham số đó. Trong trường hợp của bạn, tôi đoán bạn nên sử dụng ClassDatađối tượng làm nguồn. Ngoài ra - xin lưu ý rằng ClassDatakế thừa từ: IEnumerable<>- điều đó có nghĩa là mỗi lần một tập hợp các tham số được tạo khác sẽ được sử dụng làm tham số đến cho chức năng đang kiểm tra cho đến khi IEnumerable<>tạo ra giá trị.

Ví dụ ở đây: Tom DuPont .NET

Ví dụ có thể không chính xác - tôi đã không sử dụng xUnit trong một thời gian dài

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.