Я понимаю ценность автоматизированного тестирования и использую его там, где проблема достаточно четко определена, чтобы я мог придумать хорошие контрольные примеры. Однако я заметил, что некоторые люди здесь и в StackOverflow делают упор на тестировании только модуля, а не его зависимостей. Здесь я не вижу выгоды.
Пересмешка, чтобы избежать зависимостей тестирования, усложняет тестирование. Это добавляет искусственные требования гибкости / развязки к вашему производственному коду для поддержки имитации. (Я не согласен со всеми, кто говорит, что это способствует хорошему дизайну. Написание дополнительного кода, введение таких вещей, как структуры внедрения зависимостей или иное усложнение вашей кодовой базы, чтобы сделать вещи более гибкими / подключаемыми / расширяемыми / развязанными без реального варианта использования, - это чрезмерная инженерия, а не хороший дизайн.)
Во-вторых, тестирование зависимостей означает, что критический низкоуровневый код, который используется повсеместно, тестируется с использованием входных данных, отличных от тех, о которых явно писал тот, кто написал свои тесты. Я обнаружил множество ошибок в низкоуровневой функциональности, выполняя модульные тесты высокоуровневой функциональности, не высмеивая низкоуровневую функциональность, от которой она зависела. В идеале они были бы найдены в модульных тестах для функциональности низкого уровня, но пропущенные случаи всегда бывают.
Какова другая сторона этого? Действительно ли важно, чтобы модульный тест также не проверял его зависимости? Если так, то почему?
Изменить: я могу понять значение насмешливых внешних зависимостей, таких как базы данных, сети, веб-сервисы и т. Д. (Спасибо Анне Лир за мотивацию, чтобы я прояснил это.) Я имел в виду внутренние зависимости , то есть другие классы, статические функции и т. Д. которые не имеют никаких прямых внешних зависимостей.
Ответы:
Это вопрос определения. Тест с зависимостями - это интеграционный тест, а не модульный тест. Вы также должны иметь комплект интеграционных тестов. Разница в том, что набор интеграционных тестов может быть запущен в другой среде тестирования и, вероятно, не как часть сборки, поскольку он занимает больше времени.
Для нашего продукта: Наши модульные тесты запускаются с каждой сборкой, занимая секунды. Подмножество наших интеграционных тестов выполняется с каждой регистрацией и занимает 10 минут. Наш полный пакет интеграции работает каждую ночь, занимая 4 часа.
источник
Тестирование со всеми имеющимися зависимостями все еще важно, но это больше в области интеграционного тестирования, как сказал Джеффри Фауст.
Одним из наиболее важных аспектов модульного тестирования является обеспечение надежности ваших тестов. Если вы не верите, что проходной тест действительно означает, что все хорошо, а неудачный тест действительно означает проблему в рабочем коде, ваши тесты не настолько полезны, как могли бы.
Чтобы сделать ваши тесты заслуживающими доверия, вам нужно сделать несколько вещей, но я собираюсь сосредоточиться только на одном для этого ответа. Вы должны убедиться, что они просты в запуске, чтобы все разработчики могли легко их запустить, прежде чем регистрировать код. «Простота запуска» означает, что ваши тесты выполняются быстро, и для их запуска не требуется обширная конфигурация или настройка. В идеале, любой должен иметь возможность проверить последнюю версию кода, сразу запустить тесты и убедиться, что они пройдены.
Абстрагирование от зависимостей от других вещей (файловая система, база данных, веб-сервисы и т. Д.) Позволяет избежать необходимости конфигурирования и делает вас и других разработчиков менее восприимчивыми к ситуациям, когда возникает искушение сказать: «О, тесты не пройдены, потому что я не сетевой ресурс не настроен. Ладно. Я запусту их позже. "
Если вы хотите проверить, что вы делаете с некоторыми данными, ваши модульные тесты для этого кода бизнес-логики не должны заботиться о том, как вы получаете эти данные. Возможность протестировать основную логику вашего приложения, не завися от поддержки таких вещей, как базы данных, потрясающая. Если вы этого не делаете, вы пропускаете.
PS Я должен добавить, что определенно можно переоценить во имя тестируемости. Тест-драйв вашего приложения помогает смягчить это. Но в любом случае, плохие реализации подхода не делают подход менее актуальным. Что-нибудь может быть использовано неправильно и перестараться, если не продолжать спрашивать "почему я это делаю?" пока развивается.
Что касается внутренних зависимостей, все становится немного мутным. Мне нравится думать о том, что я хочу максимально защитить свой класс от изменений по неправильным причинам. Если у меня есть настройки, как-то так ...
Мне вообще все равно, как
SomeClass
создается. Я просто хочу использовать это. Если SomeClass изменяется и теперь требует параметров для конструктора ... это не моя проблема. Мне не нужно менять MyClass, чтобы учесть это.Теперь это касается дизайна. Что касается юнит-тестов, я также хочу защитить себя от других классов. Если я тестирую MyClass, мне нравится знать, что внешних зависимостей нет, что SomeClass в какой-то момент не вводил соединение с базой данных или какую-то другую внешнюю ссылку.
Но еще большая проблема заключается в том, что я также знаю, что результаты некоторых моих методов основаны на выводе некоторых методов в SomeClass. Без насмешек или заглушения SomeClass у меня не было бы возможности изменить этот вклад в спрос. Если мне повезет, я смогу составить свою среду внутри теста таким образом, чтобы он вызывал правильный ответ от SomeClass, но такой путь привносит сложность в мои тесты и делает их хрупкими.
Переписав MyClass, чтобы он принимал экземпляр SomeClass в конструкторе, я могу создать поддельный экземпляр SomeClass, который возвращает желаемое значение (либо через фальшивый фреймворк, либо с помощью ручного макета). Мне обычно не нужно вводить интерфейс в этом случае. Делать это или нет - это во многом личный выбор, который может быть продиктован вашим языком выбора (например, интерфейсы более вероятны в C #, но вам определенно не нужен в Ruby).
источник
Помимо проблемы с модулем и интеграционным тестом, учтите следующее.
Класс Widget имеет зависимости от классов Thingamajig и WhatsIt.
Модульный тест для Widget не пройден.
В каком классе лежит проблема?
Если вы ответили «запустите отладчик» или «прочитайте код, пока я его не найду», вы поймете, как важно проверять только модуль, а не зависимости.
источник
Представьте, что программирование похоже на приготовление пищи. Затем модульное тестирование - это то же самое, что убедиться, что ваши ингредиенты свежие, вкусные и т. Д. Принимая во внимание, что интеграционное тестирование похоже на то, чтобы убедиться, что ваша еда вкусная.
В конечном счете, обеспечение того, чтобы ваша еда была вкусной (или чтобы ваша система работала), является самой важной вещью, конечной целью. Но если ваши ингредиенты, то есть единицы, работают, у вас будет более дешевый способ выяснить это.
Действительно, если вы можете гарантировать, что ваши юниты / методы работают, у вас, скорее всего, будет функционирующая система. Я подчеркиваю «более вероятно», а не «наверняка». Вам по-прежнему нужны интеграционные тесты, точно так же, как вам нужен кто-то, кто попробует приготовленную вами еду и скажет, что конечный продукт хорош. Вам будет легче попасть туда со свежими ингредиентами, вот и все.
источник
Тестирование блоков путем их полной изоляции позволит протестировать ВСЕ возможные вариации данных и ситуаций, в которые этот блок может быть отправлен. Поскольку он изолирован от остальных, вы можете игнорировать изменения, которые не оказывают прямого влияния на тестируемый блок. это, в свою очередь, значительно уменьшит сложность тестов.
Тестирование с различным уровнем интегрированных зависимостей позволит вам протестировать определенные сценарии, которые не могли быть протестированы в модульных тестах.
Оба важны. Просто выполняя модульное тестирование, вы неизменно усложняете тонкую ошибку, возникающую при интеграции компонентов. Простое интеграционное тестирование означает, что вы тестируете систему без уверенности в том, что отдельные части машины не были протестированы. Думать, что вы можете достичь лучшего полного теста, просто выполняя интеграционное тестирование, практически невозможно, так как чем больше компонентов вы добавляете, тем быстрее становится ОЧЕНЬ быстро (подумайте факториально), и создание теста с достаточным охватом становится очень быстро невозможным.
Короче говоря, три уровня «модульного тестирования» я обычно использую почти во всех своих проектах:
Внедрение зависимостей является одним из очень эффективных средств для достижения этой цели, поскольку оно позволяет очень легко изолировать компоненты для модульных тестов, не усложняя систему. Ваш бизнес-сценарий может не оправдывать использование механизма впрыска, но ваш сценарий тестирования почти оправдан. Этого для меня достаточно, чтобы сделать тогда незаменимым. Вы также можете использовать их для независимого тестирования различных уровней абстракции с помощью тестирования частичной интеграции.
источник
Блок. Значит единственное число
Тестирование двух вещей означает, что у вас есть две вещи и все функциональные зависимости.
Если вы добавите третью вещь, вы увеличите функциональные зависимости в тестах за пределы линейного. Взаимосвязи между вещами растут быстрее, чем количество вещей.
Существует n (n-1) / 2 потенциальных зависимостей среди n тестируемых элементов.
Это большая причина.
Простота имеет значение.
источник
Помните, как вы впервые научились делать рекурсию? Мой проф сказал: «Предположим, у вас есть метод, который делает х» (например, решает Фиббоначи для любого х). «Чтобы решить для х, вы должны вызвать этот метод для х-1 и х-2». В том же духе, заглушение зависимостей позволяет вам делать вид, что они существуют, и проверять, что текущий модуль делает то, что должен делать. Конечно, предполагается, что вы проверяете зависимости так же строго.
Это по сути ПСП на работе. Сосредоточение внимания на одной ответственности даже за ваши тесты устраняет количество умственных манипуляций, которые вы должны сделать.
источник
(Это небольшой ответ. Спасибо @TimWilliscroft за подсказку.)
Неисправности легче локализовать, если:
Это хорошо работает на бумаге. Однако, как показано в описании OP (зависимости являются ошибочными), если зависимости не проверяются, было бы трудно точно определить местоположение ошибки.
источник
Много хороших ответов. Я бы также добавил пару других моментов:
Модульное тестирование также позволяет вам тестировать ваш код, когда ваши зависимости не существуют . Например, вы или ваша команда еще не написали другие слои, или, возможно, вы ждете интерфейса, предоставленного другой компанией.
Модульные тесты также означают, что вам не нужно иметь полную среду на вашем компьютере разработчика (например, базу данных, веб-сервер и т. Д.). Я бы рекомендовал сильным , что все разработчики делают имеют такую среду, однако вырубить это, для того , чтобы репро ошибок и т.д. Однако, если по каким - то причинам это не возможно , чтобы имитировать производственную среду , то модульное тестирование по крайней мере дает ю некоторый уровень уверенности в вашем коде, прежде чем он перейдет в большую тестовую систему.
источник
Об аспектах дизайна: я считаю, что даже небольшие проекты выигрывают от того, что код будет тестируемым. Вам не обязательно вводить что-то вроде Guice (часто это делает простой класс), но отделение процесса построения от логики программирования приводит к
источник
Э-э ... в этих ответах есть хорошие моменты по модульному и интеграционному тестированию!
Я скучаю по затратным и практическим взглядам здесь. Тем не менее, я ясно вижу преимущества очень изолированных / атомарных модульных тестов (возможно, очень независимых друг от друга и с возможностью запуска их параллельно и без каких-либо зависимостей, таких как базы данных, файловая система и т. Д.) И (более высокого уровня) интеграционных тестов, но ... это также вопрос затрат (времени, денег, ...) и рисков .
Итак, есть другие факторы, которые гораздо важнее (например, «что тестировать» ), прежде чем вы подумаете о «как проверить» из моего опыта ...
Платит ли мой клиент (косвенно) за дополнительное количество написания и поддержания тестов? Является ли более ориентированный на тестирование подход (сначала пишите тесты, прежде чем писать код) действительно эффективным с точки зрения затрат в моей среде (риск сбоя кода / анализ затрат, люди, спецификации проекта, настройка тестовой среды)? Код всегда глючит, но может ли быть экономически более выгодным перенести тестирование на производственное использование (в худшем случае!)?
Это также во многом зависит от того, каково качество вашего кода (стандарты) или структуры, IDE, принципы проектирования и т. Д., Которыми вы и ваша команда следите, и насколько они опытны. Хорошо написанный, легко понятный, достаточно хорошо документированный (в идеале самодокументируемый), модульный, ... код вносит, вероятно, меньше ошибок, чем наоборот. Таким образом, реальная «потребность», давление или общие затраты на обслуживание / исправление ошибок / риски для обширных тестов могут быть невысокими.
Давайте возьмем это до крайности, когда коллега из моей команды предложил, что мы должны попытаться выполнить модульное тестирование нашего чистого кода уровня модели Java EE с желаемым 100% покрытием для всех классов в пределах и насмешливых данных в базе данных. Или менеджер, который хотел бы, чтобы интеграционные тесты покрывались 100% всех возможных вариантов использования в реальном мире и рабочих процессов веб-интерфейса, потому что мы не хотим рисковать, если какой-либо вариант использования потерпит неудачу. Но у нас ограниченный бюджет около 1 миллиона евро, довольно жесткий план по кодированию всего. Клиентская среда, где потенциальные ошибки приложений не будут представлять большой опасности для людей или компаний. Наше приложение будет внутренне проверено с помощью (некоторых) важных модульных тестов, интеграционных тестов, ключевых пользовательских тестов с разработанными планами испытаний, этапом испытаний и т. Д. Мы не разрабатываем приложение для какого-либо ядерного завода или фармацевтического производства!
Я сам пытаюсь написать тест, если это возможно, и развиваюсь, пока тестирую мой код. Но я часто делаю это по принципу «сверху вниз» (интеграционное тестирование) и пытаюсь найти хорошую точку, где можно «вырезать уровень приложения» для важных тестов (часто на уровне модели). (потому что это часто много о "слоях")
Кроме того, код модульных и интеграционных тестов не оказывает негативного влияния на время, деньги, обслуживание, кодирование и т. Д. Он классный и должен применяться, но с осторожностью и с учетом последствий при запуске или после 5 лет большого количества разработанных тестов. код.
Поэтому я бы сказал, что на самом деле многое зависит от оценки доверия и затрат / рисков / выгод ... как в реальной жизни, когда вы не можете и не хотите бегать с множеством или 100% -ными механизмами безопасности.
Ребенок может и должен подняться куда-нибудь и может упасть и пораниться. Машина может перестать работать, потому что я заправил не то топливо (неверный ввод :)). Тост может быть сожжен, если кнопка времени перестала работать через 3 года. Но я никогда не хочу ехать по шоссе и держу в руках мой оторванный руль :)
источник