Классы модульного тестирования, которые создают экземпляры других классов - PullRequest
1 голос
/ 21 февраля 2020

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

Этот вопрос на самом деле не задается c для MVVM и C#, но вот что мой пример буду использовать. Обратите внимание, что я упростил это, и он не будет компилироваться как есть - цель состоит в том, чтобы показать шаблон.

class ItemListViewModel
{
  ItemListViewModel(IService service)
  {
    this.service.ItemAdded += this.OnItemAdded;
  }

  List<IItemViewModel> Items { get; }

  OnItemAdded(IItemModel addedItem)
  {
    var viewModel = new ItemViewModel(addedItem);
    this.Items.Add(viewModel);
  }
}

class ItemViewModel : IItemViewModel
{
  ItemViewModel(IItem) {}
}

Как можно видеть выше, есть событие со слоя модели. ViewModel прослушивает это событие и в ответ добавляет новую дочернюю ViewModel. Это соответствует стандартным методам объектно-ориентированного программирования, которые мне известны, а также шаблону MVVM, и мне кажется, что это довольно чистая реализация.

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

Мне кажется, что это не так, потому что Сейчас я по сути тестирую больше, чем ItemListViewModel, особенно если ItemListViewModel вызывает какие-либо методы для любого из элементов внутри. Мне нужно, чтобы ItemListViewModel зависел от макета IItemViewModels во время тестирования.

Существует несколько тактик, которые я рассмотрел, как это сделать:

  1. Пусть собственный класс ItemListViewModel прослушивает событие и добавление вычеркнутых предметов. Однако это только перемещает проблему, поскольку теперь владелец класса не может быть полностью вычеркнут.
  2. Передайте фабрику ItemViewModel в ItemListViewModel и используйте ее вместо нового. Это определенно будет работать для насмешек, поскольку он перемещает вещи, чтобы быть основанными на внедрении зависимостей ... но значит ли это, что мне нужна фабрика для каждого класса, который я когда-либо хочу смоделировать в своем приложении? Это кажется неправильным, и было бы неудобно поддерживать.
  3. Измените мою модель и как она взаимодействует с ViewModel. Возможно, шаблон событий, который я использую, не подходит для тестирования; хотя я не понимаю, как бы мне пришлось обходиться без необходимости в конечном итоге создать ItemViewModel где-то в коде, который нужно протестировать.

Кроме того, я искал в Интернете и просматривал книгу Чистый код, но это действительно не было покрыто. Все говорит о внедрении зависимости, которое явно не решает это.

Ответы [ 2 ]

1 голос
/ 22 февраля 2020

нормально ли писать мои модульные тесты в зависимости от реальной версии ItemViewModel, а не макета?

Да!
Вы должны использовать Реальная реализация, пока тесты становятся медленными или очень очень сложными в настройке.

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

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

Иногда приложение делится на независимые доменные модули, которые «общаются» друг с другом через абстрактный интерфейс. Чтобы сохранить независимость модулей, мы будем имитировать коммуникационный интерфейс, чтобы тестируемый модуль не зависел от деталей реализации другого модуля домена.

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

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

1 голос
/ 21 февраля 2020

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

Миско Хевери писал об этом паттерне: Как думать о новом операторе

Если вы смешиваете логику приложения c с построением графа ( модульное тестирование нового оператора становится невозможным ни для чего, кроме конечных узлов в вашем приложении.

Итак, если мы посмотрим на код вашей проблемы:

OnItemAdded(IItemModel addedItem)
{
  var viewModel = new ItemViewModel(addedItem);
  this.Items.Add(viewModel);
}

, то один изменение, которое мы могли бы рассмотреть, заключается в замене этого прямого вызова на ItemViewModel::new более косвенным подходом

var viewModel = factory.itemViewModel(addedItem);

Где factory обеспечивает возможность создания ItemViewModel, а дизайн позволяет нам предоставлять замены.

ItemListViewModel(IService service, Factory factory)
{
  this.service.ItemAdded += this.OnItemAdded;
  this.factory = factory;
}

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

Когда это важно? Следует отметить, что вы спрашиваете о ItemViewModel, но не спрашиваете о List . Почему это так?

Пара ответов: список стабилен; нас совсем не беспокоит, что поведение самого List будет меняться таким образом, что это приведет к заметному изменению поведения ItemListViewModel. Если тест сообщит о проблеме позже, не будет никаких сомнений в том, что мы допустили ошибку в нашем коде.

Кроме того, this.List (предположительно) изолирован. Нам не нужно беспокоиться о том, что результаты наших тестов будут нестабильными, потому что одновременно выполняется другой код. Другими словами, тест не уязвим для проблем, вызванных общим изменяемым состоянием.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...