Модульный тест с типами Generics - PullRequest
0 голосов
/ 05 января 2019

Я хочу протестировать юнит-тест с помощью дженериков, и я БОРЬБА, чтобы найти правильный путь.

У меня есть

[TestCase(typeof(CalendarGeneralCsv), typeof(CalendarGeneralCsvMap), 121)]
public void ReadFromCsvFileWithConfigurationMapTest<T,Tmap>(T t, Tmap tmap, int totalRowsExptected)
{
   //Arrange

   //Act
    var records = csvService.ReadFileCsv<T, Tmap>(_csvToRead, ",") as IEnumerable<object>;

     var result = new List<object>(records);

     //Assert
     result.Should().NotBeNullOrEmpty();
     result.Should().HaveCount(totalRowsExptected);
}

Ошибка в этой строке

  var records = csvService.ReadFileCsv<T, Tmap>(_csvToRead, ",") as IEnumerable<object>;

Сказать, что T и Tmap должны быть ссылочного типа.

Ответы [ 4 ]

0 голосов
/ 05 января 2019

Я бы обычно не отвечал там, где уже есть несколько ответов, и один из них принят, но все они, кажется, основаны на предположении, что методы тестирования не могут быть общими. Они определенно могут. Моя память говорит мне, что когда-то это было хорошо задокументировано, но, похоже, это больше не происходит - или моя память не так - что объясняет, почему вы не думаете, что это возможно.

Возможно, что общее решение может быть не лучшим, но это кажется забавной попыткой, и может быть или лучше, или прояснить, почему принятое решение лучше. Я могу пойти так далеко с уже предоставленной информацией, но если Джолинице будет сотрудничать, возможно, мы сможем чему-то научиться. : -)

Итак ... вот начальный снимок решения, которое я отредактирую, если появится дополнительная информация.

Исходное решение в вопросе вызывает ошибку, потому что ограничения в универсальном методе ReadFileCsv<T, Tmap>(...) не выполнены. Мы не знаем, что это такое, но из-за ошибки они включают T : class и Tmap : class. Таким образом, первый шаг к правильному ответу состоит в том, чтобы воспроизвести все ограничения метода, вызываемого на сам метод теста.

[TestCase(typeof(CalendarGeneralCsv), typeof(CalendarGeneralCsvMap), 121)]
public void ReadFromCsvFileWithConfigurationMapTest<T,Tmap>(int totalRowsExptected)
    where T : class
    where Tmap : class
{
   //Arrange

   //Act
    var records = csvService.ReadFileCsv<T, Tmap>(_csvToRead, ",") as IEnumerable<object>;

     var result = new List<object>(records);

     //Assert
     result.Should().NotBeNullOrEmpty();
     result.Should().HaveCount(totalRowsExptected);
}
0 голосов
/ 05 января 2019

В качестве альтернативы, если вы не планируете использовать в своем тесте несколько атрибутов TestCase с разными типами, тогда нет необходимости давать тесту какие-либо общие параметры. Вы можете просто явно передать типы в параметры типа:

public void ReadFromCsvFileWithConfigurationMapTest()
{
    //Arrange

    //Act
    var records = csvService.ReadFileCsv<CalendarGeneralCsv, CalendarGeneralCsvMap>(_csvToRead, ",") as IEnumerable<object>;

    var result = new List<object>(records);

    //Assert
    result.Should().NotBeNullOrEmpty();
    result.Should().HaveCount(121);
}

Однако если вы хотите запустить один и тот же тестовый набор для нескольких типов / значений, вы можете извлечь действительную логику теста в общий метод. Затем вы можете создать новый тест для каждого набора данных, который вы хотите проверить, явно передав типы в общий метод:

[Test]
public void ReadFromCsvFileWithConfigurationMapTest() => ReadFromCsvFile<CalendarGeneralCsv, CalendarGeneralCsvMap>(121);

[Test]
public void ReadFromCsvFileWithOtherMapTest() => ReadFromCsvFile<CalendarGeneralCsv, OtherGeneralCsvMap>(151);

private void ReadFromCsvFile<T, TMap>(int expectedValue)
{
    //Arrange

    //Act
    var records = csvService.ReadFileCsv<T, TMap>(_csvToRead, ",") as IEnumerable<object>;

    var result = new List<object>(records);

    //Assert
    result.Should().NotBeNullOrEmpty();
    result.Should().HaveCount(expectedValue);
}
0 голосов
/ 05 января 2019

Хотя уже принят принятый ответ (ПРИМЕЧАНИЕ: он изменился с тех пор, как я первоначально разместил) , я хотел бы предложить альтернативный способ использования отражения. Разделите метод тестирования на два метода: метод трамплина и универсальный метод теста . Есть несколько преимуществ:

  • Общий тестовый метод больше похож на любой другой тестовый метод. Это не имеет смешанного отражения.

  • Обычный метод испытаний может проходить обычным образом, в том числе и потому, что в нем нет смешанного отражения.

  • Изменения в тестируемом компоненте с большей вероятностью вызовут ошибку компилятора в тестовом проекте, поэтому вы знаете, что необходимо обновить общий тестовый метод и, вероятно, метод трамплина. Кроме того, из-за того, где генерируется исключение, во время выполнения становится понятнее, что это связано с отражением поддержки, а не с тем, как используется компонент.

  • Методу трамплинов не нужно ничего знать о тестируемом компоненте, только как вызвать универсальный метод теста.

  • Шаблон может быть легко и последовательно воспроизведен, потому что есть очень мало изменений.

Вот пример, основанный на вопросе и принятом ответе:

[TestCase(typeof(CalendarGeneralCsv), typeof(CalendarGeneralCsvMap), 121)]
[TestCase(typeof(CalendarCustomCsv), typeof(CalendarCustomCsvMap), 80)]
public void ReadFromCsvFileWithConfigurationMapTest(Type t, Type tmap, int totalRowsExpected)
{
    GetType().GetMethod(nameof(GenericReadFromCsvFileWithConfigurationMapTest))
        .MakeGenericMethod(t, tmap)                         // <-- Type parameters go here
        .Invoke(this, new object[] { totalRowsExpected });  // <-- inputs go here
}

public void GenericReadFromCsvFileWithConfigurationMapTest<T, Tmap>(int totalRowsExpected)
    where T : class
    where Tmap : class
{
    // Arrange

    // Act
    var records = csvService.ReadFileCsv<T, Tmap>(_csvToRead, ",") as IEnumerable<object>;

    // Assert
    records.Should().NotBeNull();

    var result = new List<object>(records);

    result.Should().NotBeNullOrEmpty();
    result.Should().HaveCount(totalRowsExptected);
}

Достопримечательности

Используется GetType(), потому что он ищет метод того же типа (тестовый класс). Это уменьшает вариации, так что рисунок можно легче воспроизвести.

Общий метод test имеет другое имя (неважно, какое оно есть, если он другой), поэтому при вызове GetMethod не нужно указывать типы параметров. Должен быть только один метод с таким именем, и он общедоступен, поэтому ему также не требуется BindingFlags. Кроме того, вы можете сделать его приватным, просто добавьте BindingFlags.NonPublic | BindingFlags.Instance. Примечание: не все версии фреймворка имеют перегрузку, принимающую BindingFlags. Вам нужно будет найти альтернативу, если вы хотите сделать ее приватной.

Общий метод испытаний должен включать ограничения. Это делает ограничения формальной частью вашего теста. Отражение не будет выполнено во время выполнения в любом случае, если ограничения не будут соблюдены, но, имея их в общем методе тестирования, вы, вероятно, напишите лучший тест с самого начала. Вы упомянули, что T и Tmap должны быть ссылочными типами, поэтому они включены выше.

Наконец, ваш трамплин способен определять несколько тестовых случаев, поскольку вы указали, что должны это делать, поэтому я включил еще один календарь и отображение выше.

0 голосов
/ 05 января 2019

Рассмотрите возможность использования отражения для вызова общего тестируемого предмета

[TestCase(typeof(CalendarGeneralCsv), typeof(CalendarGeneralCsvMap), 121)]
public void ReadFromCsvFileWithConfigurationMapTest(Type t, Type tmap, int totalRowsExptected) {
    //Arrange

    //...

    var serviceType = csvService.GetType();
    var method = serviceType.GetMethod("ReadFileCsv");
    var genericMethod = method.MakeGenericMethod(t, tmap);

    var parameters = new object[] { _csvToRead, "," };

    //Act
    var records = genericMethod.Invoke(csvService, parameters) as IEnumerable<object>;
    //Above same as csvService.ReadFileCsv<T, Tmap>(_csvToRead, ",") as IEnumerable<object>;

    //Assert
    records.Should().NotBeNull();

    var result = new List<object>(records);

    result.Should().NotBeNullOrEmpty();
    result.Should().HaveCount(totalRowsExptected);
}

Используя csvService, получить тип через GetType()

var serviceType = csvService.GetType();

для доступа к информации об участнике.

Найдите нужного участника по имени

var method = serviceType.GetMethod("ReadFileCsv");

и использование предоставленных аргументов типа для универсальных аргументов

var genericMethod = method.MakeGenericMethod(t, tmap);

Универсальный член может быть вызван в экземпляре службы с переданными аргументами.

 var records = genericMethod.Invoke(csvService, new object[] { _csvToRead, "," }) as IEnumerable<object>;
...