Как написать модульные тесты для подкласса с параметризованным базовым классом

Учитывая, что у меня есть класс в одной сборке под названием GBase с конструктором, который принимает 2 параметра, и подкласс GBase (назовем его GDerived), который принимает те же параметры, как мне разделить их, чтобы я мог модульно протестировать подкласс?

В другой сборке:

public class GBase
{
  public GBase(ParamType1 param1, ParamType2 param2)
  {
    ...
  }

  protected ParamType1 SomeProperty { get; set; }

// other stuff
}

В этой сборке:

public class GDerived : GBase
{
  public GDerived(ParamType1 param1, ParamType2 param2)
      :base(param1, param2)
  {
    // new code 

    SomeProperty = newCalculatedValue;

    // other stuff
  }

// other stuff
}

Исходный класс GBase является унаследованным кодом, как и общая структура программы — об изменении структуры не может быть и речи из-за размера кодовой базы (плюс 10 тыс. строк) — ни для одного из которых никогда не был написан модульный тест до совсем недавно.

Итак, теперь я хочу написать тест (используя NUnit) для конструктора подкласса, чтобы убедиться, что правильные свойства заполнены правильными значениями. Обратите внимание, что тестовые классы находятся в том же проекте, что и тестируемые классы.

[TestFixture]
public class GDerivedTests
{
  [Test]
  public void GDerivedConstructor_ValidParams_PropertiesSetCorrectly()
  {
    var newGDerived = new GDerived(parameter1, parameter2);

    Assert.That(SomeProperty == parameter1;
  }
}

Это очень грубое представление того, с чем нам приходится иметь дело, и есть случаи, отличные от установки свойства в базовом классе, которые нам нужно протестировать. Я просто даже не знаю точно, с чего начать. У меня есть книга Майкла Фезерса «Эффективная работа с устаревшим кодом», но она, похоже, не охватывает этот распространенный «шаблон проектирования», который широко используется во всем коде, с которым мы имеем дело. Это потому, что это так просто, что любой моргающий иджйот должен знать, как с этим справиться, или это потому, что это редкий случай? Я почему-то так не думаю, но могу ошибаться...

Один из возможных методов, о котором я подумал, - это извлечь интерфейс для базового класса и смоделировать конструктор базового класса, но я не уверен в деталях, как это сделать. Обратите внимание, что мы все относительные новички в модульном тестировании в команде, у нас нет опыта. Не новички в программировании, а только новички в модульном тестировании.

Тиа, Дэйв


person DaveN59    schedule 19.06.2017    source источник


Ответы (1)


Для начала: будьте проще! В вашем примере единственное, что вы можете проверить, это SomeProperty. Все остальное находится в базовом классе, который, как вам кажется, вы не хотите тестировать, поэтому метод тестирования GDerivedConstructor_ValidParams_PropertiesSetCorrectly() не имеет смысла. Однако в долгосрочной перспективе было бы разумно провести тесты на это.

Тесты обычно содержат три элемента, известных как AAA: Arrange, Act и Assert. Итак, напишите свой тест следующим образом:

[Test]
public void GDerivedTestOfSomeProperty()
{
    // arrange
    ParamOfSomeProperty expected = ValueWeAreLookingFor; // this is something that you
                                                         // have in newCalculatedValue

    // act
    GDerived actual = new GDerived(
        AnyValueThatMakesThisTestWork1, // maybe null?
        AnyValueThatMakesThisTestWork2); // maybe null?

    // assert
    Assert.AreEqual(expected, actual.SomeProperty);
}

Это для начала. Перейти отсюда. Вскоре вы увидите, что получаете много избыточного кода, поэтому, возможно, через некоторое время вам захочется перепроектировать его.

Насмешки имеют смысл для тестирования базового класса или когда базовый класс делает какие-то странные вещи с внедряемыми объектами. В этом случае передайте макеты вместо реальных объектов. Лично я бы использовал фиктивный фреймворк, который сделает всю работу за вас, и вы также можете использовать его для тестирования самого базового класса. Известный пример — moq.

На заметку: вам будет лучше, если вы переместите свои тестовые классы в свой собственный проект. Тестовый код не следует выпускать по разным причинам, кроме того, сборка, тестирование и развертывание могут стать проще, если они будут разделены.

person Quality Catalyst    schedule 19.06.2017
comment
Даже будучи относительным новичком, я знаком с подходом и фреймворками. :) Для начала мы используем NUnit и Moq. Мы добавляем тесты в новый код, добавленный в существующее приложение, которое до недавнего времени никогда не видело модульного теста. - person DaveN59; 20.06.2017
comment
Спасибо за отличный ответ. Как оказалось, у нас были и другие проблемы, которые были замаскированы багом в Resharper. Была опция Resharper (тестировались сборки с теневым копированием), которая не позволяла сообщать о реальных ошибках. После его отключения настоящими ошибками являются зависимости класса GBase от других классов в других сборках. У нас есть наша работа, вырезанная для нас ... - person DaveN59; 20.06.2017
comment
Что касается примечания, то в пользу обоих подходов есть вполне веские аргументы. Мы все еще обсуждаем лучший подход. - person DaveN59; 20.06.2017
comment
Для всех, кто читает это и заботится, оказывается, что нам нужно поместить модульные тесты в их собственный каталог, чтобы получить доступ ко всем проектам в нашем решении. Дискуссия разрешилась... - person DaveN59; 21.06.2017