Как должен проходить один модульный тест a.NET контроллер MVC?
Я ищу совет относительно эффективного модульного тестирования контроллеров .NET mvc.
где я работаю, многие такие тесты используют moq, чтобы издеваться над уровнем данных и утверждать, что вызываются определенные методы уровня данных. Это не кажется мне полезным, поскольку он по существу проверяет, что реализация не изменилась, а не тестирует API.
Я также читал статьи, рекомендующие такие вещи, как проверка правильности типа возвращаемой модели представления. Я могу видеть это дает некоторую ценность, но само по себе это, похоже, не заслуживает усилий по написанию многих строк насмешливого кода (модель данных нашего приложения очень большая и сложная).
может ли кто-нибудь предложить некоторые лучшие подходы к тестированию блока контроллера или объяснить, почему вышеуказанные подходы действительны/полезны?
спасибо!
6 ответов:
модульный тест контроллера должен проверять алгоритмы кода в ваших методах действий, а не в вашем слое данных. Это одна из причин, чтобы издеваться над этими службами данных. Контроллер ожидает получать определенные значения из репозиториев / сервисов / etc и действовать по-разному, когда он получает от них различную информацию.
вы пишете модульные тесты, чтобы утверждать, что контроллер ведет себя очень определенным образом в очень конкретных сценариях / обстоятельствах. Ваш слой данных-это одна часть приложения это обеспечивает эти обстоятельства для методов контроллера / действия. Утверждение, что метод службы был вызван контроллером, является ценным, потому что вы можете быть уверены, что контроллер получает информацию из другого места.
проверка типа возвращаемого viewmodel является ценным, потому что, если возвращается неправильный тип viewmodel, MVC создаст исключение времени выполнения. Вы можете предотвратить это в производстве, запустив модульный тест. Если тест не проходит, то вид может вызвать исключение в производстве.
юнит-тесты могут быть полезны, потому что они делают рефакторинг гораздо легче. Вы можете изменить реализацию и утверждать, что поведение остается прежним, убедившись, что все модульные тесты проходят.
ответ на комментарий #1
Если изменение реализации метода под тестом вызывает изменение / удаление метода с более низким уровнем, то модульный тест также должен измениться. Однако, это не должно происходить так часто, как можно подумать.
типичный рабочий процесс Red-green-refactor требует написания модульных тестов до написание методов, которые они тестируют. (Это означает, что в течение короткого периода времени ваш тестовый код не будет компилироваться, и поэтому многие молодые / неопытные разработчики испытывают трудности с принятием Red green refactor.)
Если вы сначала напишете свои модульные тесты, вы придете к точке, где вы знаете, что контроллер должен получить информацию от a нижний слой. Как вы можете быть уверены, он пытается получить эту информацию? Высмеивая метод нижнего уровня, который предоставляет информацию, и утверждая, что метод нижнего уровня вызывается контроллером.
возможно, я ошибся, когда я использовал термин "изменение реализации."Когда метод действия контроллера и соответствующий модульный тест должны быть изменены, чтобы изменить или удалить издевательский метод, вы действительно меняете поведение контроллера. Рефакторинг по определению, означает изменение реализации без изменения общего поведения и ожидаемых результатов.
Red-green-refactor-это подход к обеспечению качества, который помогает предотвратить ошибки и дефекты в коде до их появления. Обычно разработчики меняют реализацию, чтобы удалить ошибки после их появления. Поэтому, повторяю, случаи, о которых вы беспокоитесь, не должны происходить так часто, как вы думаете.
вы должны сначала поставить контроллеры на диету. Тогда вы можете удачи модульное тестирование их. Если они толстые, и вы запихнули в них всю свою бизнес-логику, я согласен, что вы будете проходить свою жизнь, издеваясь над своими модульными тестами и жалуясь, что это пустая трата времени.
когда вы говорите о сложной логике, это не обязательно означает, что эта логика не может быть разделена на разные уровни, и каждый метод должен быть проверен на единицу изоляция.
целью модульного теста является проверка поведения метода в изоляции, основанного на наборе условий. Вы устанавливаете условия теста с помощью mocks и утверждаете поведение метода, проверяя, как он взаимодействует с другим кодом вокруг него-проверяя, какие внешние методы он пытается вызвать, но особенно проверяя значение, которое он возвращает при заданных условиях.
поэтому в случае методов контроллера, которые возвращают 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); }
Я не вижу особого смысла в модульном тестировании контроллера, так как обычно это просто кусок кода, который соединяет другие части. Модульное тестирование обычно включает в себя много насмешек и просто проверяет, что другие службы подключены правильно. Сам тест является отражением кода реализации.
Я предпочитаю интеграционные тесты -- я начинаю не с конкретного контроллера, а с Url-адреса и проверяю, что возвращенная модель имеет правильные значения. С помощью Ивонны, тест может выглядеть так:
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(что может быть детализацией реализации).
Да, вы должны проверить весь путь до БД. Время, которое вы вкладываете в издевательство, меньше, и значение, которое вы получаете от издевательства, тоже очень мало(80% вероятных ошибок в вашей системе не могут быть выбраны издевательством).
когда вы тестируете весь путь от контроллера до БД или веб-службы, то это не называется модульным тестированием, а интеграционным тестированием. Я лично верю в интеграционное тестирование в отличие от модульного тестирования. И я могу сделать тест-драйв разработки успешно.
вот как это работает для нашей команды. Каждый тестовый класс В начале регенерирует БД и заполняет/заполняет таблицы с минимальным набором данных(например: роли пользователей) . На основе необходимости контроллеров мы заполняем БД и проверяем, выполняет ли контроллер свою задачу. Это разработано таким образом, что поврежденные данные БД, оставленные другими методами, никогда не провалят тест. За исключением времени, необходимого для запуска, почти все качества модульного теста (хотя это теория) являются метод Gettable.
в моей карьере было только 2% ситуаций(или очень редко), когда я был вынужден использовать насмешки/заглушки, поскольку невозможно было создать более реалистичный источник данных. Но во всех других ситуациях интеграционные тесты были возможны.
нам потребовалось время, чтобы достичь зрелого уровня с этим подходом. у нас есть хорошая структура, которая имеет дело с тестовыми данными населения и поиска(граждане первого класса). И это окупается большое время :). Первый шаг-попрощаться с издевательствами и модульные тесты. Если насмешки не имеют смысла, то они не для вас! Интеграционный тест дает вам хороший сон
===================================
редактировать после комментария ниже: демо
интеграционный тест или функциональный тест должен иметь дело непосредственно с БД. Никаких насмешек. Итак, вот эти шаги. Вы хотите проверить getEmployee(). все эти 5 шагов ниже выполняются в одном методе тестирования.
- падение DB
- создать БД и заполнить роли и другие инфра данные
- создать запись сотрудника с идентификатором
- используйте этот идентификатор и вызовите getEmployee ()
теперь Assert () / проверьте правильность возвращенных данных
Это доказывает, что getEmployee() строительство . Шаги до 3 требует, чтобы код использовался только тестовым проектом. Шаг 4 вызывает код приложения. Я имел в виду, что создание сотрудника (Шаг 2) должно быть сделано тестовый код проекта, а не код приложения. Если есть код приложения для создания сотрудника (например:CreateEmployee()) тогда это не должно использоваться. Так же, когда мы тестируем CreateEmployee() затем GetEmployee() код приложения не должен использоваться. Мы должны иметь тестовый код проекта для извлечения данных из таблицы.
таким образом, нет насмешек! Причина удаления и создания БД заключается в том, чтобы предотвратить наличие в БД неверных данных. С нашим подходом тест пройдет независимо от того, сколько раз мы его запускаем.
специальный совет: в шаге 5, если getEmployee() возвращает объект сотрудника. Если позже разработчик удаляет или изменяет имя поля, тест прерывается, потому что поля проверяются. Что делать, если разработчик добавляет новое поле позже? И он / она забывает добавить тест для него (assert)? Решение состоит в том, чтобы всегда добавлять проверку количества полей. например: объект сотрудник имеет 4 поля (Имя, Фамилия, обозначение, пол) . Так утверждают количество поля объекта сотрудник 4 . И наш тест потерпит неудачу из-за подсчета и напоминает разработчику добавить поле assert для вновь добавленного поля. А также наш тестовый код будет добавить новое поле в БД и извлекает его и проверяет.
и это отличная статья, обсуждающая преимущества интеграционное тестирование над модульным тестированием потому что "модульное тестирование убивает!"(он говорит)
обычно, когда вы говорите о модульных тестах, вы тестируете одну отдельную процедуру или метод, а не всю систему, пытаясь устранить все внешние зависимости.
Comments