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

35

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

Подводя итог его очки:

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

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

Джефф Левайн
источник
9
Вы сами сказали: написание модульных тестов дает лучший код, потому что заставляет вас писать код, который можно тестировать. Тестируемый модулем код обладает множеством очень важных свойств, которые улучшают его общее качество.
Роберт Харви
1
@ Роберт Харви - Согласен - мне было бы интересно увидеть список этих свойств.
Джефф Левайн
18
Что касается первого момента - вы в основном говорите, что недостатком модульных тестов является то, что они не работают, когда вы их не используете. То же самое можно сказать и о любой другой практике. Кроме того, использование вами пассивного голоса скрывает тот факт, что кто-то активно перекомментирует юнит-тесты. Так что, похоже, у вас нет участия разработчиков в команде, что является другой проблемой.
JacquesB
1
по теме: programmers.stackexchange.com/a/301540/134647
Ник Алексеев

Ответы:

62

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

Юнит тесты по своей сути не плохие. Но они часто проверяют детали реализации, а не поток ввода / вывода. Вы получите абсолютно бессмысленные тесты, когда это произойдет. Мое собственное правило заключается в том, что хороший юнит-тест говорит вам, что вы что-то сломали; плохой юнит-тест просто говорит вам, что вы что-то изменили.

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

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

Учитывая все вышесказанное, я был бы удивлен, если бы ваш друг так критически относился к юнит-тестам, как вам кажется. Скорее я бы понял, что он прагматичен. То есть он заметил, что написанные модульные тесты практически бессмысленны на практике. Цитата: «модульные тесты, как правило, закомментированы, а не переработаны». Для меня там есть неявное сообщение - если им, как правило, нужно переделывать, это потому, что они склонны сосать. Предполагая, что так, второе предложение следующее: разработчики будут тратить меньше времени на написание кода, который сложнее ошибиться - то есть интеграционные тесты.

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

Дени де Бернарди
источник
13
Это, это, в сто раз больше. Это отличная вещь в тестовой разработке. Написание тестов в первую очередь поможет вам написать тесты, которые не проверяют детали вашей реализации, но предполагаемое поведение, которое вы пытались реализовать.
wirrbel
9
Я думаю, что хрупкие юнит-тесты не являются неотъемлемой чертой юнит-тестирования, но вместо того, чтобы неправильно понимать, для чего существует юнит-тестирование. Нам нужно лучшее образование для разработчиков. Если вы реализуете детали реализации юнит-теста, вы делаете это неправильно . Если вы делаете частные методы общедоступными «для их проверки», вы делаете это неправильно . Всегда проверяйте открытый интерфейс и поведение, которые должны меняться реже, чем детали реализации!
Андрес Ф.
2
@DocBrown: на самом деле нет. Я имел в виду, что это так часто делают неправильно, что коллега по OP правильно делает это в большинстве случаев - или, по крайней мере, во всех случаях, с которыми я когда-либо сталкивался в одержимых юнит-тестированием компаниях.
Дени де Бернарди
3
@DenisdeBernardy: моя точка зрения такова: все, что вы написали, безусловно, правильно, с большим опытом из практики, но я упускаю вывод, что это означает для случая ОП с его коллегой, в контексте предложения «интеграционных тестов в качестве лучшая альтернатива ".
Док Браун
5
Я полностью согласен с Доком Брауном. По сути, ваша позиция заключается в том, что юнит-тесты хороши, но когда они плохо написаны, они становятся проблемой. Таким образом, вы совсем не согласны с коллегой по OP, просто вы должны убедиться, что вы действительно пишете модульные тесты, прежде чем утверждать их полезность. Я думаю, что ваш ответ очень запутанный, поскольку в первом предложении говорится, что вы согласны с коллегой ОП, когда это не так. Это больше похоже на напыщенную речь о плохо написанных модульных тестах (что, на мой взгляд, является проблемой на практике), а не в случае с модульными тестами в целом.
Винсент Савард
38

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

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

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

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

Док Браун
источник
Это очень то, что я чувствовал, когда мы обсуждали это. Предполагая, что для конкретного случая могут быть написаны полные и релевантные интеграционные тесты, создается впечатление, что модульный тест станет спорным вопросом. (Хотя сейчас, когда я пишу это, я думаю о неожиданных будущих случаях - например, о превращении нашего бэкенда в веб-сервис для другого приложения).
Джефф Левайн
+1 За «ты должен работать над своим определением». Хорошо сказано!
Андрес Ф.
14

Я не знаю, что я принимаю аргументы вашего коллеги.

  1. Если юнит-тесты закомментированы, то это потому, что кто-то ленится. Это не ошибка модульных тестов. И если вы начнете использовать ленивых в качестве оправдания, вы можете поспорить с любой практикой, требующей небольших усилий.
  2. Если вы все делаете правильно, модульные тесты не должны занимать много времени. Они не мешают вам выполнять более важную работу. Если мы все согласны с тем, что исправление испорченного кода важнее, чем что-либо еще, модульный тест является чертовски важным. Это простой способ проверить почтовые условия. Без этого, как вы узнаете, что вы выполнили условия почтовых гарантий? А если нет, ваш код не работает.

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

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

Если вы хотите научиться нюансам и философии, есть много отличных ресурсов:

Скудный Роджер
источник
+1 за эту статью, может быть лучшим ответом на дело ОП, чем все, что мы можем написать здесь
Док Браун
+1 за пункт № 1 о лени, являющейся оправданием. Я не могу не подчеркнуть, насколько это разочаровывает. Я был там. В последнее время на самом деле.
Грег Бургхардт
3
Причина 1 - это не ошибка модульных тестов, но все же причина против модульных тестов.
user253751 12.12.15
Прочитав статью DHH, убедитесь, что вы посмотрели видео, где он обсуждал эту тему с Кентом Беком и Мартином Фаулером (они на YouTube) - многие люди, похоже, получают более экстремальную версию своей идеи, чем он предполагал, и он немного уточняет это в этих дискуссиях.
Жюль
6

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

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


источник
4

модульные тесты, как правило, закомментированы, а не переработаны.

В этот момент процесс проверки кода говорит: «исправьте модульные тесты», и это больше не проблема :-) Если ваш процесс проверки кода не улавливает такого рода серьезную ошибку в представленном коде, то это проблема.

Филип Кендалл
источник
3

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

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

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

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

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

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

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

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

В этом есть доля правды. Если вы можете охватить нижние уровни программного стека тестами, ориентированными на верхние уровни программного стека, ваши тесты могут оказаться более полезными при рефакторинге кода.

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

kasperd
источник
3

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

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

Каждый метод тестирования будет неполным. Даже если вы получаете 100% покрытие по некоторым показателям, вы все равно не просматриваете почти все возможные наборы входных данных. Так что не думайте, что юнит-тесты - это магия.

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

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

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

Майкл Шоу
источник
3

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

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

Ссылаясь на замечания, сделанные вашим другом:

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

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

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

AK_
источник
2

Я могу думать только об одном аргументе против юнит-тестов, и он довольно слабый.

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

Однако: во-первых, во время написания тестов есть (небольшая) стоимость.

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

Ewan
источник
+1 за попытку искренне ответить на заданный вопрос.
RubberDuck
2
Стоимость юнит-теста определенно не мала. Хорошие модульные тесты обычно занимают больше времени, чем то, что они тестируют. В большинстве случаев преимущества превышают стоимость.
AK_ 12.12.15
1
только в теории - когда вы выполняете рефакторинг, вы получаете огромные затраты на обновление модульных тестов, поскольку они почти всегда слишком гранулированы. Если бы вы говорили о том, чтобы иметь хороший набор тестов интеграции, то вы были бы правы.
gbjbaanb
Если проект достаточно короткий и не требует постоянного обслуживания / обновлений - большинство устаревших систем начинали как короткий проект без тестов - и заканчивали тем, что нанимали больше разработчиков для поддержки растущей системы без тестов
Fabio