Подтвердить что ты не робот

Как должен быть unit test контроллер .NET MVC?

Я ищу совет относительно эффективного модульного тестирования контроллеров .NET mvc.

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

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

Может ли кто-нибудь предложить несколько лучших подходов к тестированию модулей контроллера или объяснить, почему приведенные выше подходы действительны/полезны?

Спасибо!

4b9b3361

Ответ 1

Контроллер unit test должен проверять алгоритмы кода в ваших методах действий, а не на вашем уровне данных. Это одна из причин издеваться над этими службами данных. Контроллер ожидает получить определенные значения из репозиториев/сервисов/etc и действовать по-разному, когда получает от них различную информацию.

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

Проверка типа возвращаемой модели представления является ценной, поскольку, если возвращается неправильный тип viewmodel, MVC будет генерировать исключение во время выполнения. Это можно предотвратить, если запустить unit test. Если тест не удался, тогда представление может вызвать исключение в производстве.

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

Ответить на комментарий # 1

Если изменение реализации метода-under-test вызывает изменение/удаление метода издевательства на нижнем уровне, тогда также должен измениться unit test. Однако это не должно происходить так часто, как вы думаете.

Типичный рабочий процесс red-green-refactor требует для написания ваших модульных тестов до написания методов, которые они тестируют. (Это означает, что в течение короткого промежутка времени ваш тестовый код не будет компилироваться, и поэтому многие молодые/неопытные разработчики с трудом принимают красный зеленый рефактор.)

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

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

Red-green-refactor - это подход обеспечения качества, который помогает предотвратить ошибки и ошибки в коде, прежде чем они появятся. Обычно разработчики меняют реализацию, чтобы удалить ошибки после их появления. Поэтому, чтобы повторить, случаи, о которых вы беспокоитесь, не должны происходить так часто, как вы думаете.

Ответ 2

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

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

Ответ 3

Точкой unit test является проверка поведения метода по отдельности на основе набора условий. Вы устанавливаете условия теста с помощью mocks и утверждаете поведение метода, проверяя, как он взаимодействует с другим кодом вокруг него - путем проверки того, какие внешние методы он пытается вызвать, но, в частности, путем проверки значения, которое он возвращает с учетом условий.

Итак, в случае методов Controller, возвращающих ActionResults, очень полезно проверить значение возвращаемого ActionResult.

Взгляните на раздел "Создание тестов устройств для контроллеров" здесь для некоторых очень четких примеров, используя Moq.

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

[TestMethod]
public void CreateInvalidContact()
{
    // Arrange
    var contact = new Contact();
    _service.Expect(s => s.CreateContact(contact)).Returns(false);
    var controller = new ContactController(_service.Object);

    // Act
    var result = (ViewResult)controller.Create(contact);

    // Assert
    Assert.AreEqual("Create", result.ViewName);
}

Ответ 4

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

Я предпочитаю тесты интеграции - я начинаю не с конкретного контроллера, а с Url и проверяю, что возвращаемая модель имеет правильные значения. С помощью Ivonna тест может выглядеть так:

var response = new TestSession().Get("/Users/List");
Assert.IsInstanceOf<UserListModel>(response.Model);

var model = (UserListModel) response.Model;
Assert.AreEqual(1, model.Users.Count);

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

Ответ 5

Да, вы должны полностью протестировать БД. Время, в которое вы насмехаетесь, меньше, и ценность, которую вы получаете от насмешек, очень меньше (80% вероятных ошибок в вашей системе нельзя отбросить из-за насмешек).

Когда вы проверяете весь путь от контроллера к БД или веб-службе, это не называется модульным тестированием, а интеграционным тестированием. Я лично верю в интеграционное тестирование, а не в модульное тестирование. И я могу успешно выполнить тестовое развитие.

Вот как это работает для нашей команды. Каждый тестовый класс в начале восстанавливает БД и заполняет/сортирует таблицы с минимальным набором данных (например, роли пользователя). На основе потребностей контроллеров мы заполняем БД и проверяем, выполняет ли контроллер его задачу. Это спроектировано таким образом, что поврежденные DB данные, оставленные другими методами, никогда не завершится. За исключением времени, которое нужно выполнить, почти все качества unit test (хотя это теория) являются gettable.

В моей карьере было всего 2% ситуаций (или очень редко), когда я был вынужден использовать mocks/stubs, так как было невозможно создать более реалистичный источник данных. Но во всех других ситуациях интеграционные тесты были возможны.

Нам потребовалось время, чтобы достигнуть зрелого уровня с помощью этого подхода. у нас есть хорошая структура, которая касается совокупности и поиска тестовых данных (граждан первого класса). И это окупает большое время:). Первый шаг - попрощаться с издевательствами и модульными тестами. Если насмешки не имеют смысла, то они не для вас! Тест интеграции дает вам хороший сон

===================================

Отредактировано после комментария ниже: Демо

Интеграционный тест или функциональный тест должен иметь дело с БД напрямую. Никаких насмешек. Итак, это шаги. Вы хотите протестировать getEmployee(). все эти 5 шагов ниже выполняются одним методом испытаний.

  • Drop DB
  • Создание БД и заполнение ролей и других информационных данных
  • Создать запись сотрудника с идентификатором
  • Используйте этот идентификатор и вызывайте getEmployee()
  • Теперь Assert()/Проверьте правильность возвращаемых данных

    Это доказывает, что работает getEmployee(). Шаги до 3 требуют, чтобы код использовался только тестовым проектом. Шаг 4 вызывает код приложения. То, что я имел в виду, это создание сотрудника (шаг 2), должно выполняться кодом кода проекта, а не кодом приложения. Если для создания сотрудника существует код приложения (например: CreateEmployee()), это не должно использоваться. Точно так же, когда мы тестируем CreateEmployee(), тогда GetEmployee() код приложения не должен использоваться. Мы должны иметь тестовый код проекта для извлечения данных из таблицы.

Таким образом, нет издевательств! Причиной отказа и создания БД является предотвращение ошибочных данных БД. С нашим подходом тест пройдет независимо от того, сколько раз мы его запускаем.

Специальный совет: на шаге 5, если getEmployee() возвращает объект employee. Если позднее разработчик удаляет или изменяет имя поля, тест прерывается, потому что поля проверяются. Что делать, если разработчик добавит новое поле позже? И он/она забывает добавить тест для него (утверждать)? Решение состоит в том, чтобы всегда добавлять проверку количества полей. например: Объект Employee имеет 4 поля (Имя, Фамилия, Обозначение, Пол). Таким образом, количество полей объекта сотрудника - 4. И наш тест завершится неудачно из-за подсчета и напомнит разработчику добавить поле подтверждения для вновь добавленного поля. А также наш тестовый код добавит это новое поле в БД и получит его и проверит.

И это отличная статья, в которой обсуждаются преимущества интеграционное тестирование по модульным тестированию

Ответ 6

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

Другими словами, при тестировании контроллера вы пишете метод тестирования методом, и вам не нужно даже загружать представление или модель, это те части, которые вам нужно "издеваться". Затем вы можете изменить mocks для возврата значений или ошибок, которые трудно воспроизвести при других тестах.