Вопрос: Должны ли вы использовать методы «Verify» и «VerifyAll», предоставленные Moq в ваших модульных тестах?


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


6


источник


Ответы:


Макетные тесты

Там много споров вокруг хрупкости издевательств в модульных тестах и ​​хорошо ли они или нет. Я лично считаю, что это компромисс между ремонтопригодностью и надежностью. Чем больше вы помещаете свой производственный код в единичное испытательное давление , тестируя его в изоляции с помощью mocks, менее эффективные реализации пройдут тесты. Таким образом, вы можете заставить свой производственный код повысить надежность и хороший дизайн. С другой стороны, он привязывается к конкретной реализации и увеличивает нагрузку на обслуживание, потому что больше изменений необходимо будет изменить, как только будет изменена информация о реализации.

Синтаксис VerifyAll ()

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

Использование VerifyAll () в каждом тестовом методе  

Это в лучшем случае избыточное и в худшем случае наносит ущерб вашему тестовому набору.

  • Как правило, единичный тест должен проверять только одно. Систематический вызов VerifyAll() в дополнение к вашему нормальному утверждению приносит путаницу - если тест не удается, вы не можете точно знать, что пошло не так.

  • Что касается читаемости, вы просто добавляете шум к каждому из своих тестов. Это очень сложно, просто прочитав тестовый метод, чтобы отследить, что VerifyAll() действительно означает.

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

Поэтому, если вам действительно нужно использовать VerifyAll(), лучше написать отдельные тесты для него ИМО.


5



Прежде всего, важно понять, что Verify-фамильные методы существуют по какой-то причине - они позволяют вам тестировать ненаблюдаемы 1  поведение  вашей системы. Что я имею в виду? Рассмотрим простой пример создания и отправки отчетов. Ваш последний компонент, скорее всего, будет выглядеть так:

public void SendReport(DateTime reportDate, ReportType reportType)
{
    var report = generator.GenerateReport(reportDate, reportType);
    var reportAsPlainText = converter.ConvertReportToText(report);
    reportSender.SendEmailToSubscribers(body: reportAsPlainText);
}

Как вы протестируете этот метод? Он ничего не возвращает, поэтому вы не можете проверить значения. Он не изменяет состояние системы (например, переворачивает некоторый флаг), поэтому вы также не можете проверить это. Единственный видимый результат SendReportвызванный тем фактом, что отчет был отправлен через SendEmailToSubscribers призывание. В этом главная ответственность SendReport метод - и это то, что должны проверять модульные тесты.

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

В заключение, Рой Ошероув в своей книге Искусство модульного тестирования (2-е издание)  разделяет единичные тесты на три категории, в зависимости от что можно проверить :

  • возвращаемое значение метода (простое, общее)
  • изменение состояния системы (простое, редкое)
  • вызов внешнего компонента (сложный, редкий)

Последняя категория - это то, где вы используете Verify методы на них. Для остальных двух достаточно заглушек ( Setup методы).

Когда ваш код будет спроектирован правильно, такой тест будет (последняя категория) находиться в меньшинстве в вашей кодовой базе, где-то в диапазоне 5% - 10% (номер, взятый из книги Роя, в соответствии с моими наблюдениями).


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


12



Предпочтительным способом является использование AAA. Но для внешних зависимостей с типом возврата void (скажем, void WriteData (данные данных)), Verify может быть полезен (или Setup, а затем VerifyAll).


1