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

Значение модульных тестов высокого уровня и макет объектов

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

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

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

Я ненавижу, записывая единичные тесты для этого типа кода. Единичные тесты состоят почти полностью из макетной установки объекта. Строка за строкой unit test читается почти как зеркальный образ реализации. Фактически, я пишу модульные тесты, глядя на реализацию. "Сначала я утверждаю, что этот макет-метод вызывается, тогда я утверждаю, что этот макет-метод называется..." и т.д. Я должен тестировать поведение метода, а не то, что он вызывает правильную последовательность методов. Другое дело: я обнаружил, что эти тесты чрезвычайно хрупки для рефакторинга. Если тест настолько хрупкий, что он полностью разрушается и должен быть переписан, когда тестируемый код подвергается рефакторингу, то не потеряно ли одно из основных преимуществ модульного тестирования?

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

4b9b3361

Ответ 1

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

Модульные тесты важны, потому что они говорят вам, когда вы что-то ломаете во время рефакторинга.

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

Приемочные испытания - это то, что клиент подписывает. Приемочные тесты обычно пишутся другими людьми (а не программистом), чтобы обеспечить другую перспективу; программисты склонны писать тесты на то, что работает, тестеры пытаются сломать его, проверяя, что не работает.

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

Ответ 2

В стороне

Просто коснуться вашего смелого заявления:

"Я должен тестировать поведение метод, а не то, что он вызывает правильная последовательность методов"

Поведение объекта под тестом - последовательность действий, которые он принимает. Это на самом деле "поведение" тестирования, тогда как когда вы говорите "поведение метода", я думаю, вы имеете в виду проверку состояния, как и в, дать ему вход и проверить правильный вывод.

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

Ответ

В стороне, я лично никогда не пишу подробные тесты для слоя пользовательского интерфейса. Если вы используете шаблон MVVM, MVP или MVC для своего приложения, то на уровне "1-девелоперская группа" это умаляет и контрпродуктивно для меня. Я вижу ошибки в пользовательском интерфейсе, и да, насмешливое поведение на этом уровне имеет тенденцию быть хрупким. Я гораздо больше забочусь о том, чтобы мои базовые области домена и DAL выполнялись правильно.

Что представляет ценность на верхнем уровне, это тест интеграции. Получил веб-приложение? Вместо того, чтобы утверждать, что ваши методы контроллера возвращают ActionResult (тест с небольшим значением), напишите тест интеграции, который запрашивает все страницы вашего приложения, и убедитесь, что нет 404 или 403. Запустите его один раз при каждом развертывании.

Окончательный ответ

Я всегда следую правилу 80/20 с модульным тестированием. Чтобы получить этот последний 20% -ный охват на высоком уровне, о котором вы говорите, это будет 80% ваших усилий. Для моих личных и большинства моих рабочих проектов это не окупается.

Короче говоря, я согласен. Я бы написал интеграционные тесты и проигнорировал модульные тесты для описываемого вами кода.

Ответ 3

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

Главным моментом тестирования Mock является то, что менеджеры могут утверждать, что метрика покрытия кода находится в Foo%.... так что все должно работать! Единственный исключительный случай, когда они, возможно, полезны, - это когда вам нужно протестировать класс, который является огромной болью, чтобы воссоздать аутентично (например, тестирование класса Action в Struts).

Я большой сторонник написания сырых тестов. Реальный код с реальными объектами. Код внутри методов будет меняться со временем, но цель и, следовательно, общее поведение обычно не происходит.

Ответ 4

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

И иногда вполне возможно не писать тест для определенного метода.

Ответ 5

В общем, я считаю, что тестирование этого типа метода/команды будет созревать для уровня тестирования интеграции. В частности, я "unit test" для небольших команд низкого уровня, которые (как правило) не имеют побочных эффектов. Если я действительно хочу, чтобы unit test что-то, что не соответствует этой модели, первое, что я делаю, это увидеть, могу ли я реорганизовать/перепроектировать, чтобы он соответствовал.

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

  • Запишите, как он был вызван для получения данных
  • Возвращенные сохраненные данные Запишите, как это было.
  • Запишите, как он был вызван для ввода управляемых данных.