У вас есть класс X, и вы пишете несколько модульных тестов, которые проверяют поведение X1. Есть также класс A, который принимает X в качестве зависимости.
Когда вы пишете модульные тесты для A, вы имитируете X. Другими словами, во время модульного тестирования A вы устанавливаете (постулируете) поведение макета X для X1. Время идет, люди используют вашу систему, потребности в изменениях, X развивается: вы модифицируете X, чтобы показать поведение X2. Очевидно, что модульные тесты для X не пройдут, и вам нужно будет их адаптировать.
Но что с А? Модульные тесты для A не будут терпеть неудачу, когда поведение X изменено (из-за насмешки над X). Как определить, что результат A будет отличаться при запуске с «реальным» (модифицированным) X?
Я ожидаю ответов по типу: «Это не цель модульного тестирования», но какое значение тогда имеет модульное тестирование? Действительно ли это говорит вам только о том, что, когда все тесты пройдены, вы не внесли существенных изменений? И когда поведение какого-то класса изменяется (добровольно или невольно), как вы можете обнаружить (предпочтительно автоматическим способом) все последствия? Разве мы не должны больше фокусироваться на интеграционном тестировании?
источник
X1
вы говорите, чтоX
реализует интерфейсX1
. Если изменить интерфейсX1
кX2
Мок , который вы использовали в других тестах не должно составлять больше, поэтому вы вынуждены исправить эти тесты тоже. Изменения в поведении класса не должны иметь значения. Фактически, ваш классA
не должен зависеть от деталей реализации (что вы и должны изменить в этом случае). Таким образом, модульные тесты дляA
по-прежнему правильны, и они говорят вам, чтоA
работает при идеальной реализации интерфейса.Ответы:
Вы? Я не знаю, если только я не должен. Я должен, если:
X
медленно илиX
имеет побочные эффектыЕсли ни то, ни другое не применимо, мои тесты тоже
A
пройдутX
. Делать что-либо еще - значит проводить изолирующие тесты до нелогичного предела.Если у вас есть части вашего кода, использующие макеты других частей вашего кода, то я бы согласился: в чем смысл таких модульных тестов? Так что не делай этого. Пусть эти тесты используют реальные зависимости, поскольку таким образом они образуют гораздо более ценные тесты.
И если некоторые люди расстраиваются из-за того, что вы называете эти тесты «модульными тестами», просто назовите их «автоматизированными тестами» и продолжайте писать хорошие автоматические тесты.
источник
Вам нужны оба. Модульные тесты, чтобы проверить поведение каждого из ваших модулей, и несколько интеграционных тестов, чтобы убедиться, что они подключены правильно. Проблема, связанная с использованием только интеграционных испытаний, заключается в комбинаторном взрыве в результате взаимодействия между всеми вашими устройствами.
Допустим, у вас есть класс A, который требует 10 модульных тестов, чтобы полностью охватить все пути. Затем у вас есть другой класс B, который также требует 10 модульных тестов, чтобы покрыть все пути, которые код может пройти через него. Теперь, скажем, в вашем приложении вам нужно передать вывод A в B. Теперь ваш код может пройти 100 различных путей от ввода A до вывода B.
При модульных тестах вам нужно всего лишь 20 модульных тестов + 1 интеграционный тест, чтобы полностью охватить все случаи.
С интеграционными тестами вам потребуется 100 тестов, чтобы охватить все пути кода.
Вот очень хорошее видео о недостатках полагаться только на интеграционные тесты. Интегрированные тесты JB Rainsberger являются Scam HD
источник
Вау, подожди минутку. Последствия тестов на провал X слишком важны, чтобы их замаскировать.
Если изменение реализации X с X1 на X2 нарушает модульные тесты для X, это означает, что вы внесли обратно несовместимое изменение в контракт X.
X2 не является X, в смысле Лискова , поэтому вам следует подумать о других способах удовлетворения потребностей ваших заинтересованных сторон (например, о введении новой спецификации Y, которая реализована в X2).
Для более глубокого понимания смотрите Pieter Hinjens: Конец версий программного обеспечения или Rich Hickey Simple Made Easy .
С точки зрения A, существует предварительное условие, что соавтор соблюдает договор X. И ваше наблюдение заключается в том, что изолированный тест для A не дает вам никакой уверенности в том, что A распознает соавторов, которые нарушают X-договор.
Обзор Интегрированные тесты являются мошенничеством ; на высоком уровне ожидается, что у вас будет столько изолированных тестов, сколько вам нужно, чтобы убедиться, что X2 правильно реализует контракт X, и столько изолированных тестов, сколько вам нужно, чтобы убедиться, что A делает правильные вещи, учитывая интересные ответы от X, и немного меньшее количество интегрированных тестов, чтобы гарантировать, что X2 и A согласуются с тем, что означает X.
Вы иногда будете видеть это различие, выраженное как одиночные тесты против
sociable
тестов; см. Jay Fields, эффективно работающий с юнит-тестами .Опять же, видеть, что интегрированные тесты - это афера - Райнсбергер подробно описывает цикл положительной обратной связи, который является общим (в его опыте) для проектов, которые полагаются на интегрированные (написание нот) тесты. Таким образом, без изолированных / одиночных испытаний, оказывающих давление на проект , качество ухудшается, что приводит к большему количеству ошибок и более комплексным испытаниям ....
Вам также понадобятся (некоторые) интеграционные тесты. В дополнение к сложности, представленной несколькими модулями, выполнение этих тестов имеет тенденцию иметь большее сопротивление, чем изолированные тесты; более эффективно повторять очень быстрые проверки во время работы, сохраняя дополнительные проверки, когда вы думаете, что «сделали».
источник
Позвольте мне начать с того, что основная предпосылка вопроса неверна.
Вы никогда не тестируете (или имитируете) реализации, вы тестируете (и имитируете) интерфейсы .
Если у меня есть реальный класс X, который реализует интерфейс X1, я могу написать фиктивный XM, который также соответствует X1. Тогда мой класс A должен использовать что-то, что реализует X1, который может быть классом X или фиктивным XM.
Теперь предположим, что мы изменили X, чтобы реализовать новый интерфейс X2. Ну, очевидно, мой код больше не компилируется. A требует что-то, что реализует X1, и что больше не существует. Проблема была выявлена и может быть исправлена.
Предположим, что вместо замены X1 мы просто изменим его. Теперь с классом А все готово. Однако макет XM больше не реализует интерфейс X1. Проблема была выявлена и может быть исправлена.
Вся основа для модульного тестирования и макета состоит в том, что вы пишете код, который использует интерфейсы. Потребитель интерфейса не заботится о том, как реализован код, а только о том, что соблюдается тот же контракт (входы / выходы).
Это ломается, когда у ваших методов есть побочные эффекты, но я думаю, что их можно смело исключить, так как «не может быть проверено модулем или проверено».
источник
Принимая ваши вопросы по очереди:
Они дешевы, чтобы написать и запустить, и вы получите раннюю обратную связь. Если вы сломаете X, вы узнаете более или менее немедленно, если у вас есть хорошие тесты. Даже не думайте писать интеграционные тесты, если вы не проверили все свои уровни модульно (да, даже в базе данных).
Проходящие тесты могут на самом деле сказать вам очень мало. Возможно, вы не написали достаточно тестов. Возможно, вы не проверили достаточно сценариев. Здесь может помочь покрытие кода, но это не серебряная пуля. У вас могут быть тесты, которые всегда проходят. Следовательно, красный - часто пропускаемый первый шаг красного, зеленого - рефакторинг.
Больше тестирования - хотя инструменты становятся все лучше и лучше. НО вы должны определять поведение класса в интерфейсе (см. Ниже). NB всегда будет место для ручного тестирования на вершине пирамиды тестирования.
Все больше интеграционных тестов также не являются ответом, их дорого писать, запускать и поддерживать. В зависимости от настроек вашей сборки, менеджер сборки может в любом случае исключить их, что делает их зависимыми от запоминания разработчиком (никогда не бывает хорошо!).
Я видел, как разработчики часами пытались исправить сломанные интеграционные тесты, которые они нашли бы за пять минут, если бы у них были хорошие юнит-тесты. Если это не удастся, попробуйте просто запустить программное обеспечение - это все, о чем будут заботиться ваши конечные пользователи. Нет смысла иметь миллион юнит-тестов, которые пройдут, если весь карточный домик рухнет, когда пользователь запускает весь пакет.
Если вы хотите убедиться, что класс A использует класс X таким же образом, вам следует использовать интерфейс, а не конкретную модель. Тогда наиболее вероятные изменения будут обнаружены во время компиляции.
источник
Это правильно.
Модульные тесты предназначены для проверки изолированной функциональности модуля, на первый взгляд проверяя, что он работает как задумано и не содержит глупых ошибок.
Модульные тесты не предназначены для проверки работоспособности всего приложения.
Многие забывают, что модульные тесты - это самое быстрое и грязное средство проверки вашего кода. Как только вы узнаете, как работают ваши маленькие подпрограммы, вам также нужно будет выполнить интеграционные тесты. Модульное тестирование само по себе лишь незначительно лучше, чем отсутствие тестирования.
Причина, по которой у нас вообще есть юнит-тесты, в том, что они должны быть дешевыми. Быстро создавать, запускать и поддерживать. Как только вы начинаете превращать их в минимальные интеграционные тесты, вы попадаете в мир боли. С таким же успехом вы можете пройти полное Интеграционное тестирование и вообще проигнорировать юнит-тестирование.
сейчас некоторые люди думают, что единица - это не просто функция в классе, а весь класс (включая меня). Однако все, что это делает, - это увеличивает размер устройства, поэтому вам может потребоваться меньше интеграционных тестов, но оно все еще нужно. До сих пор невозможно проверить, что ваша программа делает то, что должна, без полного набора тестов интеграции.
и затем вам все равно нужно будет выполнить полное интеграционное тестирование в действующей (или полуживой) системе, чтобы проверить, работает ли она с условиями, которые использует клиент.
источник
Модульные тесты ничего не доказывают. Это верно для всех тестов. Обычно модульные тесты сочетаются с контрактным проектированием (проектирование по контракту - это еще один способ сказать это) и, возможно, с автоматическим подтверждением правильности, если правильность необходимо проверять на регулярной основе.
Если у вас есть реальные контракты, состоящие из инвариантов классов, предварительных условий и пост-условий, можно иерархически доказать правильность, основывая правильность компонентов более высокого уровня на контрактах компонентов более низкого уровня. Это фундаментальная концепция дизайна по контракту.
источник
fac(5) == 120
, вы не доказали, чтоfac()
действительно возвращает факториал его аргумента. Вы только доказали, чтоfac()
возвращает факториал пять, когда вы проходите5
. И даже в этом нет уверенности, поскольку,fac()
возможно,42
вместо этого можно было бы вернуться в первые понедельники после полного затмения в Тимбукту ... Проблема в том, что вы не можете доказать соответствие, проверяя отдельные входные данные теста, вам необходимо проверить все возможные входные данные, а также докажите, что вы ничего не забыли (например, чтение системных часов).Я нахожу сильно смоделированные тесты редко полезными Большую часть времени я заканчиваю тем, что переопределяю поведение, которое уже имеет исходный класс, что полностью отрицает цель насмешек.
Моя лучшая стратегия - хорошо разделить проблемы (например, вы можете протестировать часть A своего приложения, не вводя части B через Z). Такая хорошая архитектура действительно помогает написать хороший тест.
Кроме того, я более чем готов принять побочные эффекты, если могу откатить их, например, если мой метод изменяет данные в БД, пусть это будет! Пока я могу откатить БД в предыдущее состояние, какой вред? Кроме того, есть преимущество в том, что мой тест может проверить, выглядят ли данные, как ожидалось. Здесь очень помогают БД в памяти или специальные тестовые версии БД (например, тестовая версия RavenDB в памяти).
Наконец, мне нравится издеваться над границами сервиса, например, не делать этот http-вызов сервису b, но давайте перехватим его и представим подходящего
источник
Я бы хотел, чтобы люди в обоих лагерях поняли, что классовые и поведенческие тесты не ортогональны.
Классовые и юнит-тесты используются взаимозаменяемо и, возможно, не должны. Некоторые юнит-тесты просто реализованы в классах. Вот и все. Модульное тестирование проводилось десятилетиями на языках без классов.
Что касается тестирования поведения, это вполне возможно сделать в рамках тестирования класса с использованием конструкции GWT.
Кроме того, то, будут ли ваши автоматические тесты проходить в соответствии с классом или поведением, зависит от ваших приоритетов. Некоторым нужно будет быстро создавать прототипы и получать что-то за дверью, в то время как другие будут иметь ограничения по охвату из-за стиля дома. Много причин. Они оба совершенно правильные подходы. Вы платите свои деньги, вы выбираете.
Итак, что делать, когда код ломается. Если он был закодирован для интерфейса, то нужно изменить только конкретность (вместе с любыми тестами).
Однако введение нового поведения не должно ставить под угрозу систему вообще. Linux и др. Полны устаревших функций. И такие вещи, как конструкторы (и методы) могут быть успешно перегружены без необходимости изменения всего вызывающего кода.
Когда классовое тестирование выигрывает, вам нужно внести изменения в класс, который еще не подключен (из-за нехватки времени, сложности или чего-то еще). Начать работу с классом намного проще, если у него есть комплексные тесты.
источник
Если интерфейс для X не изменен, вам не нужно менять модульный тест для A, потому что ничего, связанного с A, не изменилось. Похоже, вы действительно написали модульный тест X и A вместе, но назвали его модульным тестом A:
В идеале макет X должен имитировать все возможные варианты поведения X, а не только поведение, которое вы ожидаете от X. Поэтому, независимо от того, что вы на самом деле реализуете в X, A уже должна быть в состоянии справиться с этим. Таким образом, никакие изменения в X, кроме изменения самого интерфейса, не окажут влияния на модульное тестирование для A.
Например: предположим, что A является алгоритмом сортировки, а A предоставляет данные для сортировки. Макет X должен содержать нулевое возвращаемое значение, пустой список данных, один элемент, несколько элементов, которые уже отсортированы, несколько элементов, которые еще не отсортированы, несколько элементов, отсортированных назад, списки с повторением одного и того же элемента, нулевые значения смешаны, смешно большое количество элементов, и это также должно вызвать исключение.
Поэтому, возможно, X изначально возвращал отсортированные данные по понедельникам и пустые списки по вторникам. Но теперь, когда X возвращает несортированные данные по понедельникам и генерирует исключения по вторникам, A все равно - эти сценарии уже были рассмотрены в модульном тесте A.
источник
Вы должны посмотреть на разные тесты.
Сами юнит-тесты будут проверять только X. Они существуют, чтобы предотвратить изменение поведения X, но не защитить всю систему. Они гарантируют, что вы можете провести рефакторинг своего класса, не внося изменений в поведение. И если вы сломаете X, вы сломали это ...
A действительно должен макетировать X для своих модульных тестов, и тест с Mock должен продолжать проходить даже после того, как вы измените его.
Но существует более одного уровня тестирования! Есть также интеграционные тесты. Эти тесты предназначены для проверки взаимодействия между классами. Эти тесты обычно имеют более высокую цену, так как они не используют насмешки для всего. Например, интеграционный тест может фактически записать запись в базу данных, а модульный тест не должен иметь внешних зависимостей.
Также, если X нуждается в новом поведении, было бы лучше предоставить новый метод, который обеспечил бы желаемый результат
источник