Как вы обнаруживаете проблемы с зависимостями в модульных тестах, когда используете фиктивные объекты?

98

У вас есть класс X, и вы пишете несколько модульных тестов, которые проверяют поведение X1. Есть также класс A, который принимает X в качестве зависимости.

Когда вы пишете модульные тесты для A, вы имитируете X. Другими словами, во время модульного тестирования A вы устанавливаете (постулируете) поведение макета X для X1. Время идет, люди используют вашу систему, потребности в изменениях, X развивается: вы модифицируете X, чтобы показать поведение X2. Очевидно, что модульные тесты для X не пройдут, и вам нужно будет их адаптировать.

Но что с А? Модульные тесты для A не будут терпеть неудачу, когда поведение X изменено (из-за насмешки над X). Как определить, что результат A будет отличаться при запуске с «реальным» (модифицированным) X?

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

bvgheluwe
источник
36
В дополнение ко всем предложенным ответам я должен сказать, что я не согласен со следующим утверждением: «Действительно ли оно говорит вам только о том, что, когда все тесты пройдены, вы не внесли существенных изменений?» Если вы действительно думаете, что устранение страха перед рефакторингом не имеет большого значения, вы на пути к написанию не поддерживаемого кода
crizzis
5
Модульное тестирование показывает, ведет себя ли ваш модуль кода так, как вы ожидаете. Не более или менее. Макеты и тестовые двойники создают искусственную контролируемую среду, в которой вы можете использовать свой блок кода (изолированно), чтобы убедиться, что он соответствует вашим ожиданиям. Не более или менее.
Роберт Харви
2
Я считаю, что ваша предпосылка неверна. Когда вы упоминаете, X1вы говорите, что Xреализует интерфейс X1. Если изменить интерфейс X1к X2Мок , который вы использовали в других тестах не должно составлять больше, поэтому вы вынуждены исправить эти тесты тоже. Изменения в поведении класса не должны иметь значения. Фактически, ваш класс Aне должен зависеть от деталей реализации (что вы и должны изменить в этом случае). Таким образом, модульные тесты для Aпо-прежнему правильны, и они говорят вам, что Aработает при идеальной реализации интерфейса.
Бакуриу
5
Я не знаю как вы, но когда мне придется работать на базе кода без тестов, я до смерти боюсь, что-то сломаю. И почему? Потому что так часто случается, что что-то ломается, когда это не было предназначено. И благослови сердца нашего тестера, они не могут проверить все. Или даже близко. Но модульный тест с удовольствием скучает по скучной рутине после скучной рутины.
CorsiKa

Ответы:

125

Когда вы пишете модульные тесты для A, вы издеваетесь над X

Вы? Я не знаю, если только я не должен. Я должен, если:

  1. X медленно или
  2. X имеет побочные эффекты

Если ни то, ни другое не применимо, мои тесты тоже Aпройдут X. Делать что-либо еще - значит проводить изолирующие тесты до нелогичного предела.

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

И если некоторые люди расстраиваются из-за того, что вы называете эти тесты «модульными тестами», просто назовите их «автоматизированными тестами» и продолжайте писать хорошие автоматические тесты.

Дэвид Арно
источник
94
@Laiv, Нет, юнит-тесты должны действовать как единое целое, т.е. запускаться изолированно от других тестов . Узлы и графики можно взять в поход. Если я смогу провести изолированное сквозное тестирование без побочных эффектов за короткое время, это модульное тестирование. Если вам не нравится это определение, назовите его автоматическим тестом и прекратите писать дерьмовые тесты, чтобы соответствовать глупой семантике.
Дэвид Арно,
9
@DavidArno Увы, существует очень широкое определение изолированности. Некоторые хотели бы, чтобы «блок» включал промежуточный уровень и базу данных. Они могут верить всему, что им нравится, но есть вероятность, что при разработке любого размера колеса оторвутся в довольно короткие сроки, когда менеджер по сборке выбросит их. Как правило, если они изолированы от сборки (или эквивалент), это нормально. Примечание: если вы кодируете интерфейсы, тогда гораздо проще добавить насмешку и DI позже.
Робби Ди,
13
Вы выступаете за другой вид теста, а не отвечаете на вопрос. Это верный аргумент, но это довольно закулисный способ сделать это.
Фил Фрост,
17
@PhilFrost, чтобы процитировать себя: « И если некоторые люди расстраиваются из-за того, что вы называете эти тесты« модульными тестами », просто назовите их« автоматизированными тестами »и продолжайте писать хорошие автоматические тесты. « Пишите полезные тесты, а не глупые тесты. это просто соответствует некоторому случайному определению слова. Или, в качестве альтернативы, примите, что, возможно, у вас неправильное определение «модульного теста» и что вы перестали использовать ложные показания, потому что у вас оно неверно. В любом случае, вы получите лучшие тесты.
Дэвид Арно,
30
Я с @DavidArno на этом; моя стратегия тестирования изменилась после просмотра этого выступления Иана Купера: vimeo.com/68375232 . Чтобы свести это к одному предложению: не проверяйте классы . Тест поведения . Ваши тесты не должны знать о внутренних классах / методах, используемых для реализации желаемого поведения; они должны знать только открытую поверхность вашего API / библиотеки, и они должны это проверить. Если у тестов слишком много знаний, то вы тестируете детали реализации, и ваши тесты становятся хрупкими, связаны с вашей реализацией и фактически просто привязывают вас к шее.
Ричибан
79

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

Допустим, у вас есть класс A, который требует 10 модульных тестов, чтобы полностью охватить все пути. Затем у вас есть другой класс B, который также требует 10 модульных тестов, чтобы покрыть все пути, которые код может пройти через него. Теперь, скажем, в вашем приложении вам нужно передать вывод A в B. Теперь ваш код может пройти 100 различных путей от ввода A до вывода B.

При модульных тестах вам нужно всего лишь 20 модульных тестов + 1 интеграционный тест, чтобы полностью охватить все случаи.

С интеграционными тестами вам потребуется 100 тестов, чтобы охватить все пути кода.

Вот очень хорошее видео о недостатках полагаться только на интеграционные тесты. Интегрированные тесты JB Rainsberger являются Scam HD

Eternal21
источник
1
Я уверен, что не случайно, что вопросительный знак об эффективности интеграционных тестов шел рука об руку с юнит-тестами, охватывающими все больше слоев.
Робби Ди,
16
Да, но нигде ваши 20 юнит-тестов не требуют насмешек. Если у вас есть 10 тестов A, которые охватывают все A, и 10 тестов, которые охватывают весь B, а также повторно тестируйте 25% A в качестве бонуса, это кажется между «хорошо» и «хорошо». Поддумывать тесты A в Bs кажется очень глупым (если только на самом деле нет причины, по которой проблема A является проблемой, например, это база данных или она несет в себе множество других вещей)
Ричард Тингл,
9
Я не согласен с идеей, что достаточно одного интеграционного теста, если вы хотите получить полное покрытие. Реакции B на то, что выводит A, будут варьироваться в зависимости от выхода; если изменение параметра в A изменяет его вывод, то B может обрабатывать его некорректно.
Матье М.
3
@ Eternal21: Моя точка зрения заключалась в том, что иногда проблема заключается не в индивидуальном поведении, а в неожиданном взаимодействии. То есть, когда клей между A и B ведет себя неожиданно в некотором сценарии. Таким образом, и A, и B действуют в соответствии со спецификациями, и счастливый случай работает, но на некоторых входах есть ошибка в клеевом коде ...
Matthieu M.
1
@MatthieuM. Я бы сказал, что это выходит за рамки модульного тестирования. Клеевой код может сам подвергаться модульному тестированию, в то время как взаимодействие между A и B посредством клеевого кода является интеграционным тестом. При обнаружении конкретных крайних случаев или ошибок их можно добавить в модульные тесты связующего кода и в конечном итоге проверить в интеграционном тестировании.
Эндрю Т Финнелл
72

Когда вы пишете модульные тесты для A, вы имитируете X. Другими словами, во время модульного тестирования A вы устанавливаете (постулируете) поведение макета X для X1. Время идет, люди используют вашу систему, потребности в изменениях, X развивается: вы модифицируете X, чтобы показать поведение X2. Очевидно, что модульные тесты для X не пройдут, и вам нужно будет их адаптировать.

Вау, подожди минутку. Последствия тестов на провал 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, эффективно работающий с юнит-тестами .

Разве мы не должны больше фокусироваться на интеграционном тестировании?

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

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

VoiceOfUnreason
источник
8
Это должен быть принятый ответ. Этот вопрос описывает ситуацию, когда поведение класса было изменено несовместимым образом, но внешне выглядит идентично. Проблема здесь заключается в дизайне приложения, а не в модульных тестах. То, как это обстоятельство будет выявлено при тестировании, - это использование интеграционного теста между этими двумя классами.
Ник Коад
1
«без изолированных / одиночных испытаний, оказывающих давление на конструкцию, качество ухудшается». Я думаю, что это важный момент. Наряду с проверками поведения, у юнит-тестов есть побочный эффект, заставляющий вас иметь более модульный дизайн.
MickaëlG
Я полагаю, что это все правда, но как это поможет мне, если внешняя зависимость вносит обратное несовместимое изменение в контракт X? Возможно, класс, выполняющий ввод-вывод в библиотеке, нарушает совместимость, и мы издеваемся над X, потому что не хотим, чтобы модульные тесты в CI зависели от тяжелого ввода-вывода. Я думаю, что ОП просит проверить это, и я не понимаю, как это отвечает на вопрос. Как проверить это?
Геррит
15

Позвольте мне начать с того, что основная предпосылка вопроса неверна.

Вы никогда не тестируете (или имитируете) реализации, вы тестируете (и имитируете) интерфейсы .

Если у меня есть реальный класс X, который реализует интерфейс X1, я могу написать фиктивный XM, который также соответствует X1. Тогда мой класс A должен использовать что-то, что реализует X1, который может быть классом X или фиктивным XM.

Теперь предположим, что мы изменили X, чтобы реализовать новый интерфейс X2. Ну, очевидно, мой код больше не компилируется. A требует что-то, что реализует X1, и что больше не существует. Проблема была выявлена ​​и может быть исправлена.

Предположим, что вместо замены X1 мы просто изменим его. Теперь с классом А все готово. Однако макет XM больше не реализует интерфейс X1. Проблема была выявлена ​​и может быть исправлена.


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

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

Vlad274
источник
11
Этот ответ делает много предположений, которые не должны соблюдаться. Во-первых, это приблизительно предполагает, что мы находимся в C # или Java (или, точнее, что мы находимся на скомпилированном языке, что у языка есть интерфейсы, и что X реализует интерфейс; ни одно из них не должно быть правдой). Во-вторых, предполагается, что любое изменение поведения или «контракта» X требует изменения интерфейса (как это понимает компилятор), который реализует X. Это явно не так, даже если мы находимся в Java или C #; Вы можете изменить реализацию метода, не меняя его сигнатуру.
Марк Амери
6
@MarkAmery Это правда, что терминология «интерфейс» более специфична для C # или Java, но я думаю, что дело в том, чтобы предполагать определенный «контракт» поведения (и если это не кодифицировано, то автоматическое обнаружение этого невозможно). Вы также совершенно правы в том, что реализация может быть изменена без изменения контракта. Но изменение в реализации без изменения интерфейса (или контракта) не должно влиять на любого потребителя. Если поведение A зависит от того, как реализован интерфейс (или контракт), тогда невозможно (осмысленно) модульное тестирование.
Vlad274
1
«Вы также совершенно правы в том, что реализация может быть изменена без изменения контракта», хотя это также верно, но это не то, что я пытался сделать. Скорее, я утверждаю различие между контрактом (понимание программистом того, что должен делать объект, возможно, указанный в документации) и интерфейсом (список сигнатур методов, понятных компилятору) и говорю, что контракт может быть изменены без изменения интерфейса. Все функции с одинаковой сигнатурой являются взаимозаменяемыми с точки зрения системы типов, но не в реальности!
Марк Амери
4
@MarkAmery: Я не думаю, что Влад использует слово «интерфейс» в том же смысле, в каком вы его используете; как я читаю ответ, речь идет не об интерфейсах в узком смысле C # / Java (то есть о наборе сигнатур методов), а в общем смысле этого слова, как, например, в терминах «интерфейс прикладного программирования» или даже « пользовательский интерфейс". [...]
Илмари Каронен
6
@IlmariKaronen Если Влад использует «интерфейс» для обозначения «контракта», а не в узком смысле C # / Java, тогда утверждение «Теперь давайте предположим, что мы изменим X для реализации нового интерфейса X2. Что ж, очевидно, мой код больше не компилируется. " просто ложно, так как вы можете изменить контракт, не меняя сигнатуры методов. Но, честно говоря, я думаю, что проблема здесь в том, что Влад не использует одно и то же значение последовательно, а объединяет их - это то, что ведет к утверждению, что любое изменение в контракте X1 обязательно вызовет ошибку компиляции, не замечая , что это неверно ,
Марк Амери
9

Принимая ваши вопросы по очереди:

какое значение тогда имеет юнит-тестирование

Они дешевы, чтобы написать и запустить, и вы получите раннюю обратную связь. Если вы сломаете X, вы узнаете более или менее немедленно, если у вас есть хорошие тесты. Даже не думайте писать интеграционные тесты, если вы не проверили все свои уровни модульно (да, даже в базе данных).

Действительно ли это говорит вам только о том, что когда все тесты пройдены, вы не вносите критических изменений?

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

И когда поведение какого-то класса изменяется (добровольно или невольно), как вы можете обнаружить (желательно автоматически) все последствия

Больше тестирования - хотя инструменты становятся все лучше и лучше. НО вы должны определять поведение класса в интерфейсе (см. Ниже). NB всегда будет место для ручного тестирования на вершине пирамиды тестирования.

Разве мы не должны больше фокусироваться на интеграционном тестировании?

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

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

Если вы хотите убедиться, что класс A использует класс X таким же образом, вам следует использовать интерфейс, а не конкретную модель. Тогда наиболее вероятные изменения будут обнаружены во время компиляции.

Робби Ди
источник
9

Это правильно.

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

Модульные тесты не предназначены для проверки работоспособности всего приложения.

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

Причина, по которой у нас вообще есть юнит-тесты, в том, что они должны быть дешевыми. Быстро создавать, запускать и поддерживать. Как только вы начинаете превращать их в минимальные интеграционные тесты, вы попадаете в мир боли. С таким же успехом вы можете пройти полное Интеграционное тестирование и вообще проигнорировать юнит-тестирование.

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

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

gbjbaanb
источник
2

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

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

Фрэнк Хилман
источник
Модульные тесты ничего не доказывают . Не уверен, что я понимаю это, модульные тесты проверяют свои собственные результаты наверняка? Или вы имели в виду, что поведение не может быть доказано правильным, поскольку оно может охватывать несколько слоев?
Робби Ди
7
@RobbieDee Я думаю, он имел в виду, что когда вы проверяли fac(5) == 120, вы не доказали, что fac()действительно возвращает факториал его аргумента. Вы только доказали, что fac()возвращает факториал пять, когда вы проходите 5. И даже в этом нет уверенности, поскольку, fac()возможно, 42вместо этого можно было бы вернуться в первые понедельники после полного затмения в Тимбукту ... Проблема в том, что вы не можете доказать соответствие, проверяя отдельные входные данные теста, вам необходимо проверить все возможные входные данные, а также докажите, что вы ничего не забыли (например, чтение системных часов).
Мастер
1
@RobbieDee Тесты (включая модульные тесты) - плохая замена, часто лучшая из доступных для реальной цели, проверенная машинами доказательство. Рассмотрим все пространство состояний тестируемых модулей, включая пространство состояний любых компонентов или макетов в них. Если у вас нет очень ограниченного пространства состояний, тесты не могут охватывать это пространство состояний. Полное покрытие будет доказательством, но это доступно только для крошечных пространств состояний, например, для тестирования одного объекта, содержащего один изменяемый байт или 16-битное целое число. Автоматизированные доказательства намного более ценны.
Фрэнк Хайлеман,
1
@cmaster Вы очень хорошо суммировали разницу между тестом и доказательством. Спасибо!
Фрэнк Хилман
2

Я нахожу сильно смоделированные тесты редко полезными Большую часть времени я заканчиваю тем, что переопределяю поведение, которое уже имеет исходный класс, что полностью отрицает цель насмешек.

Моя лучшая стратегия - хорошо разделить проблемы (например, вы можете протестировать часть A своего приложения, не вводя части B через Z). Такая хорошая архитектура действительно помогает написать хороший тест.

Кроме того, я более чем готов принять побочные эффекты, если могу откатить их, например, если мой метод изменяет данные в БД, пусть это будет! Пока я могу откатить БД в предыдущее состояние, какой вред? Кроме того, есть преимущество в том, что мой тест может проверить, выглядят ли данные, как ожидалось. Здесь очень помогают БД в памяти или специальные тестовые версии БД (например, тестовая версия RavenDB в памяти).

Наконец, мне нравится издеваться над границами сервиса, например, не делать этот http-вызов сервису b, но давайте перехватим его и представим подходящего

Кристиан Зауэр
источник
1

Я бы хотел, чтобы люди в обоих лагерях поняли, что классовые и поведенческие тесты не ортогональны.

Классовые и юнит-тесты используются взаимозаменяемо и, возможно, не должны. Некоторые юнит-тесты просто реализованы в классах. Вот и все. Модульное тестирование проводилось десятилетиями на языках без классов.

Что касается тестирования поведения, это вполне возможно сделать в рамках тестирования класса с использованием конструкции GWT.

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

Итак, что делать, когда код ломается. Если он был закодирован для интерфейса, то нужно изменить только конкретность (вместе с любыми тестами).

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

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

Бельгийская собака
источник
Где у вас есть конкремент, я бы написал реализацию . Это калька из другого языка (я бы сказал, французский) или есть важное различие между конкрецией и реализацией ?
Питер Тейлор
0

Если интерфейс для X не изменен, вам не нужно менять модульный тест для A, потому что ничего, связанного с A, не изменилось. Похоже, вы действительно написали модульный тест X и A вместе, но назвали его модульным тестом A:

Когда вы пишете модульные тесты для A, вы имитируете X. Другими словами, во время модульного тестирования A вы устанавливаете (постулируете) поведение макета X для X1.

В идеале макет X должен имитировать все возможные варианты поведения X, а не только поведение, которое вы ожидаете от X. Поэтому, независимо от того, что вы на самом деле реализуете в X, A уже должна быть в состоянии справиться с этим. Таким образом, никакие изменения в X, кроме изменения самого интерфейса, не окажут влияния на модульное тестирование для A.

Например: предположим, что A является алгоритмом сортировки, а A предоставляет данные для сортировки. Макет X должен содержать нулевое возвращаемое значение, пустой список данных, один элемент, несколько элементов, которые уже отсортированы, несколько элементов, которые еще не отсортированы, несколько элементов, отсортированных назад, списки с повторением одного и того же элемента, нулевые значения смешаны, смешно большое количество элементов, и это также должно вызвать исключение.

Поэтому, возможно, X изначально возвращал отсортированные данные по понедельникам и пустые списки по вторникам. Но теперь, когда X возвращает несортированные данные по понедельникам и генерирует исключения по вторникам, A все равно - эти сценарии уже были рассмотрены в модульном тесте A.

Moby Disk
источник
Что если X просто переименовал индекс в возвращаемом массиве из foobar в foobarRandomNumber, как я могу рассчитывать с этим? если вы понимаете мою точку зрения, это, в основном, моя проблема, я переименовал возвращаемый столбец из secondName в фамилию, классическая задача, но мой тест никогда не узнает, так как его поиздевались. У меня просто такое странное чувство, как будто многие люди в этом вопросе никогда не пробовали что-то подобное, прежде чем комментировать
FantomX1
Компилятор должен был обнаружить это изменение и выдать вам ошибку компилятора. Если вы используете что-то вроде Javascript, то я рекомендую либо переключиться на Typescript, либо использовать компилятор, такой как Babel, который может обнаружить это.
Moby Disk
Что если я использую массивы в PHP, или в Java, или в Javascript, если вы измените индекс массива или удалите его, ни один из этих языков не сообщит вам об этом, индекс может быть вложен в 36-е продуманное число вложенный уровень массива, поэтому я думаю, что компилятор не является решением для этого.
FantomX1
-2

Вы должны посмотреть на разные тесты.

Сами юнит-тесты будут проверять только X. Они существуют, чтобы предотвратить изменение поведения X, но не защитить всю систему. Они гарантируют, что вы можете провести рефакторинг своего класса, не внося изменений в поведение. И если вы сломаете X, вы сломали это ...

A действительно должен макетировать X для своих модульных тестов, и тест с Mock должен продолжать проходить даже после того, как вы измените его.

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

Также, если X нуждается в новом поведении, было бы лучше предоставить новый метод, который обеспечил бы желаемый результат

Хайко Хатцфельд
источник