Передати складні параметри [Теорія]


98

Xunit має приємну особливість : ви можете створити один тест з Theoryатрибутом і помістити дані в InlineDataатрибути, а xUnit згенерує багато тестів і перевірить їх усі.

Я хочу мати що - щось на зразок цього, але параметри в мій метод не «прості дані» (як string, int, double), але список мого класу:

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

3
Якщо це має сенс у вашому середовищі, ви можете зробити це в F # з набагато менше шуму: - stackoverflow.com/a/35127997/11635
Рубен Bartelink

1
Повне керівництво, яке надсилає складні об'єкти як параметр до складних типів
Іман Бахрампор,

Відповіді:


137

У xxxxDataXUnit є багато атрибутів. Перевірте, наприклад, PropertyDataатрибут.

Ви можете реалізувати властивість, яка повертається IEnumerable<object[]>. Кожен, object[]який цей метод генерує, буде потім "розпакований" як параметри для одного виклику вашого [Theory]методу.

Інший варіант ClassData, який працює так само, але дозволяє легко поділити 'генератори' між тестами в різних класах / просторах імен, а також відокремлює «генератори даних» від фактичних методів тестування.

Див. Т. Е. Ці приклади звідси :

Приклад 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 }
            };
        }
    }
}

Приклад 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: Так, я фактично шукаю деякі оригінальні документи
xunit

2
@Nick: Я згоден , що це схоже на PropertyData, але і ви вказали причину цього: static. Саме тому я б цього не зробив. ClassData - це коли потрібно сховатися від статики. Роблячи це, ви можете використовувати повторно (тобто гніздо) генератори простіше.
quetzalcoatl

1
Будь-які ідеї, що сталося з ClassData? Я не можу знайти його в xUnit2.0, наразі я використовую MemberData зі статичним методом, який створює новий екземпляр класу і повертає це.
Ерті-Кріс Еелмаа

14
@Erti, використовуйте [MemberData("{static member}", MemberType = typeof(MyClass))]для заміни ClassDataатрибута.
Junle Li

6
З C # 6 рекомендується використовувати nameofключове слово замість жорсткого кодування імені властивості (перерви легко, але безшумно).
сара

40

Щоб оновити відповідь @ Quetzalcoatl: Атрибут [PropertyData]був замінений, [MemberData]який бере в якості аргументу назву рядка будь-якого статичного методу, поля чи властивості, що повертає IEnumerable<object[]>. (Мені здається, що особливо приємно мати метод ітератора, який може насправді обчислювати тестові випадки один за одним, піднімаючи їх під час обчислення.)

Кожен елемент у послідовності, повернуту перечислювачем, є, object[]і кожен масив повинен бути однакової довжини, і ця довжина повинна бути числом аргументів у вашому тестовому випадку (анотовано з атрибутом, [MemberData]і кожен елемент повинен мати той же тип, що і відповідний параметр методу . (А може, вони можуть бути конвертованими типами, я не знаю.)

(Див. Примітки до випуску для xUnit.net, березень 2014 року та власне виправлення з прикладом коду .)


2
@davidbak Кодплекс пішов. Посилання не працює
Kishan Vaishnav

11

Створення анонімних масивів об’єктів - не найпростіший спосіб побудови даних, тому я використав цей шаблон у своєму проекті

Спочатку визначте кілька багаторазових загальних класів

//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;
    }

}

Тепер ваші індивідуальні дані тесту та членів простіші для запису та чистіші ...

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());
        }
    }
}

DescriptionВластивість струни полягає в тому, щоб кинути собі кістки, коли один з ваших численних тестових випадків зазнає невдачі


1
Мені подобається це; він має деякий реальний потенціал для дуже складного об'єкта. Я повинен перевірити перевірки 90+ властивостей. Я можу передавати простий об’єкт JSON, десеріалізувати його та генерувати дані для тестової ітерації. Хороша робота.
Густін

1
чи не змішані параметри IsValid Testmethod - чи не повинен це бути IsValid (інгредієнт, exprectedResult, testDescription)?
макаронні вироби

9

Припустимо, у нас є складний автомобільний клас, який має клас виробника:

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; }
}

Ми будемо заповнювати та проходити клас Авто на тест Теорія.

Тому створіть клас "CarClassData", який повертає екземпляр класу "Автомобіль", як показано нижче:

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();
    }

Настав час створити метод тесту (CarTest) і визначити автомобіль як параметр:

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

в теорії складний тип

Щасти


3
Ця відповідь явно вирішує питання про передачу користувальницького типу як введення теорії, який, здається, відсутній у вибраній відповіді.
JD Cain

1
Це саме той варіант використання, який я шукав, а саме, як передати складний тип як параметр у Теорію. Працює ідеально! Це справді окупається за тестування шаблонів MVP. Зараз я можу налаштувати безліч різних примірників перегляду у всіляких станах і передавати їх у ту саму Теорію, яка перевіряє ефекти, які мають методи Presenter на цей вид. ЛЮБИТЬ це!
Денис М. Кухня

3

Можна спробувати так:

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);
    }   
}

Створіть інший клас для зберігання даних тесту:

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

Для моїх потреб я просто хотів запустити серію "тестових користувачів" через деякі тести - але [ClassData] тощо здавалося непосильним для того, що мені потрібно (адже список елементів був локалізований для кожного тесту).

Тож я зробив наступне з масивом всередині тесту - індексується зовні:

[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);
}

Це досягло моєї мети, зберігаючи чіткість намірів тесту. Вам просто потрібно синхронізувати індекси, але це все.

У результатах добре виглядає, його можна розгорнути, і ви можете повторно запустити певний екземпляр, якщо ви отримаєте помилку:

введіть тут опис зображення


"Виглядає добре в результатах, він згортається, і ви можете повторно запустити певний екземпляр, якщо ви отримаєте помилку". Дуже хороший момент. Основним недоліком, MemberDataздається, є те, що ви не можете бачити та не запускати тест із певним введенням тесту. Це відстій.
Олівер Пірмен

Насправді, я щойно розробив, що це можливо, MemberDataякщо ви використовуєте TheoryDataта за бажанням IXunitSerializable. Більше інформації та приклади тут ... github.com/xunit/xunit/isissue/429#issuecomment-108187109
Олівер Пірмен

1

Ось як я вирішив вашу проблему, у мене був той самий сценарій. Отже, вбудовані користувацькі об’єкти та різна кількість об’єктів під час кожного запуску.

    [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);
    }

Отже, це мій модульний тест, зверніть увагу на параметр params . Це дозволяє надсилати різну кількість об'єктів. А тепер мій клас DeviceTelemetryTestData :

    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();
    }

Сподіваюся, це допомагає!


-1

Думаю, ти тут помилився. Що Theoryнасправді означає атрибут xUnit : Ви хочете перевірити цю функцію, надіславши спеціальні / випадкові значення як параметри, які отримує ця функція під тестом. Це означає, що ви визначаєте в якості наступного атрибута, наприклад: InlineData, PropertyData, ClassDataі т.д .. буде джерелом для цих параметрів. Це означає, що ви повинні побудувати об'єкт-джерело для надання цих параметрів. У вашому випадку я думаю, ви повинні використовувати ClassDataоб'єкт як джерело. Також - будь ласка, зверніть увагу, що ClassDataуспадковує від: IEnumerable<>- це означає, що кожного разу, коли інший набір згенерованих параметрів буде використовуватися як вхідні параметри для функції, що перевіряється, поки не буде отримано IEnumerable<>значення.

Приклад тут: Tom DuPont .NET

Приклад може бути невірним - я довго не користувався xUnit

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.