2012-01-11 4 views
53

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

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

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

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

Спасибо!

ответ

42

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

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

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

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

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

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

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

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

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

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

+0

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

+0

Я до сих пор не согласен с идеей тестирования того, что функция вызывает другую функцию, но я понимаю, откуда вы пришли, когда вы думаете об этом с точки зрения «тестов в первую очередь». Кроме того, вы, вероятно, правы, что во многих случаях изменение в вызовах службы указывает на изменение поведения контроллера. Спасибо за очень продуманный, описательный и продуманный ответ! – ChaseMedallion

1

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

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

9

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

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

Обратитесь к разделу 'Создание тестов устройств для контроллеров'here for some very clear examples с использованием 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); 
} 
7

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

Я предпочитаю интеграционные тесты. Я начинаю не с конкретного контроллера, а с помощью 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, и воссоздать его с каждым новым испытанием, вместе с необходимыми данными. Это делает мои тесты достаточно быстрыми, но вместо сложного насмешки я делаю их понятными, например. просто создайте и сохраните экземпляр пользователя, а не mock UserService (который может быть деталью реализации).

+1

Я думаю, вы пропустили понимание идеи модульного тестирования. Единичный тест должен тестировать кусок кода (единицы) при отделении от других уровней/зависимостей и т. Д. В вашем случае вы должны написать отдельные тесты для маршрутов MVC, действия контроллера и службы. Это то, что вы описали здесь, это сквозные тесты, и хорошие инструменты для такого рода тестов - это, например, SpecFlow и Watin. –

+1

Спасибо, я это знаю. Обратите внимание, что я написал «Я предпочитаю интеграционные тесты». WatiN не позволит мне исследовать мою модель, например. – ulu

21

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

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

+9

Ссылка * поставить ваши контроллеры на диету * отсутствует :( –

+2

Я думаю, что это ссылка Дарин указывала на: https://www.youtube.com/watch?v=8TYJjRxXnXs – ryanulit

+0

Я думал, что буду редактировать и изменил ссылку на вашу, отличная работа @ryanulit! – Bart

6

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

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

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

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

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

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

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

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

  1. Капля DB
  2. Создание БД и заполнить роли и другие данные инфра
  3. Создать запись сотрудника с ID
  4. Используйте этот идентификатор и вызовите getEmployee()
  5. Теперь Assert()/Проверьте если возвращаемые данные верны

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

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

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

И это большая статья обсуждает преимущества integration testing over unit testing

+1

Мне нравится ваш подход. Я предпочитаю интеграционное тестирование, так как он может показать мне, работает ли система. Узел теста, похоже, имеет дело только с логической частью, которая в порядке, но не кажется достаточной для меня. – Mariusz

+0

Лучше всего выполнить тестирование базы данных, а затем провести простой интеграционный тест в тестовом проекте mvc, или у вас есть один интеграционный тест в тестовом проекте mvc, который также проверяет возвращенные данные? – Muflix

+2

Да. Интеграционный тест или функциональный тест должен иметь дело с БД напрямую. Никаких издевок. Я отредактировал ответ с демонстрационными шагами. –

Смежные вопросы