Если ваш код модульного теста «пахнет», это действительно имеет значение?

52

Обычно я просто собираю свои модульные тесты, используя копирование и вставку, и другие виды плохой практики. Модульные тесты обычно выглядят довольно некрасиво, они полны «запаха кода», но действительно ли это имеет значение? Я всегда говорю себе, пока «реальный» код «хорош», и это все, что имеет значение. Кроме того, модульное тестирование обычно требует различных "вонючих взломов", таких как функции заглушки.

Насколько меня должны беспокоить плохо спроектированные ("вонючие") модульные тесты?

Buttons840
источник
8
Насколько сложно исправить код, который вы всегда копируете?
Джефф
7
Запах кода в тестах может быть индикатором скрытого запаха в остальной части кода - зачем подходить к тестам, как будто они не являются «настоящим» кодом?
ХорусКол

Ответы:

75

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

Вот некоторые общие запахи модульного тестирования:

  • Хрупкость : ваши тесты часто и неожиданно терпят неудачу даже на кажущиеся тривиальными или несвязанными изменениями кода?
  • Утечка состояния : ваши тесты не проходят по-разному в зависимости, например, от того, в каком порядке они выполняются?
  • Установка / Разрушение Bloat : Ваши блоки установки / разрыва длинные и растут дольше? Они выполняют какую-либо бизнес-логику?
  • Slow Runtime : Ваши тесты занимают много времени? Разве для выполнения какого-либо отдельного юнит-теста требуется больше десятой доли секунды? (Да, я серьезно, одна десятая секунды.)
  • Трение : мешают ли существующие тесты писать новые тесты? Вы часто сталкиваетесь с ошибками при рефакторинге?

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

Вот, с другой стороны, некоторые хорошие практики для модульных тестов:

  • Быстрая сфокусированная обратная связь . Ваши тесты должны быстро изолировать сбой и дать вам полезную информацию о его причине.
  • Минимизируйте расстояние кода теста : должен быть четкий и короткий путь между тестом и кодом, который его реализует. Большие расстояния создают излишне длинные петли обратной связи.
  • Тестируйте одно за раз : модульные тесты должны тестировать только одно. Если вам нужно проверить другую вещь, напишите другой тест.
  • Ошибка - это тест, который вы забыли написать : что вы можете узнать из этой неудачи, чтобы написать более качественные, более полные тесты в будущем?
Рейн Хенрикс
источник
2
«Юнит-тесты служат другой цели и имеют другой набор напряжений, которые определяют их дизайн». Например, они должны DAMP, а не обязательно DRY.
Йорг Миттаг
3
@ Йорг Согласен, но DAMP на самом деле означает что-то? : D
Рейн Хенрикс
5
@ Rein: Описательные и значимые фразы. Также не совсем СУХОЙ. См. Codehelter.wordpress.com/2011/04/07/…
Роберт Харви
+1. Я также не знал значения DAMP.
egarcia
2
@ironcode Если вы не можете работать на одной системе изолированно, не боясь нарушить ее интеграцию с другими системами, это пахнет как тесная связь со мной. Именно в этом и заключается цель выявления запахов при испытаниях, например, при длительном времени работы: они сообщают вам о проблемах проектирования, таких как жесткое соединение Ответ не должен быть «О, тогда этот тестовый запах недействителен», он должен быть «Что этот запах говорит мне о моем дизайне?» Модульных тестов, которые определяют внешний интерфейс вашей системы, должно быть достаточно, чтобы сказать вам, нарушают ли ваши изменения интеграцию с потребителями.
Рейн Хенрикс
67

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

jayraynet
источник
17
+1 В тот момент, когда вы начинаете игнорировать провальные тесты, они не добавляют никакой ценности.
19

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

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

Джошуа Смит
источник
4
+1 вы должны поддерживать тесты, так же, как производственный код
Хэмиш Смит
Еще одна очень хорошая книга на эту тему - «Тестовые шаблоны xUnit - тестовый код рефакторинга» Джерарда Месароса.
adrianboimvaser
7

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

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

Люк
источник
5
+1: не суетиться над кодом модульного теста. Некоторые из самых глупых вопросов вращаются вокруг того, чтобы сделать код модульного теста более «устойчивым», чтобы изменение программы не нарушало модульный тест. Глупость. Модульные тесты предназначены для хрупких. Это нормально, если они менее надежны, чем код приложения, который они предназначены для тестирования.
С.Лотт
1
@ S.Lott Оба согласны и не согласны, по причинам, лучше объясненным в моем ответе.
Рейн Хенрикс
1
@ Rein Henrichs: это гораздо более серьезные запахи, чем описано в вопросе. «копировать и вставлять» и «выглядеть ужасно» не звучат так плохо, как запах, который вы описываете, когда тесты ненадежны.
S.Lott
1
@ S.Lott, я думаю, что если мы собираемся поговорить о «запахах модульного тестирования», важно провести различие. Запахов кода в модульных тестах часто нет. Они написаны для разных целей, а напряженность, которая делает их вонючими в одном контексте, сильно отличается в другом.
Рейн Хенрикс
2
@ S.Lott, кстати, кажется, что это прекрасная возможность использовать функцию чата, которой очень пренебрегают :)
Рейн Хенрикс
6

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

Ремонтопригодность

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

  • Используйте фальшивый фреймворк, чтобы ваши тесты не зависели ни от чего внешнего
  • По возможности любите заглушки над макетами (если ваша структура различает их)
  • Нет логики в тестах! If, переключатели, for-eaches, case, try-catches и т. Д. - все это большие запреты, поскольку они могут вносить ошибки в сам код теста.

читабельность

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

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

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

Вы сказали:

Модульное тестирование обычно требует различных «вонючих взломов», таких как функции заглушки.

Похоже, вы могли бы определенно ознакомиться с некоторыми приемами модульного тестирования, такими как использование Mocking Framework, чтобы сделать вашу жизнь намного проще. Я очень и очень рекомендую «Искусство модульного тестирования» , которое охватывает все вышеперечисленное и многое другое. Я нашел это полезным после долгой борьбы с плохо написанными, не поддерживаемыми, «вонючими» тестами. Это одна из лучших инвестиций времени, которые я сделал в этом году!

mjhilton
источник
Отличный ответ. У меня есть только одна небольшая ошибка: гораздо более важным, чем «одно логическое утверждение на тест», является одно действие на тест. Дело не в том, сколько шагов требуется для организации теста, должен быть только один «тестируемый код производственного действия». Если указанное действие должно иметь более одного побочного эффекта, у вас вполне может быть несколько утверждений.
Разочарован
5

Два вопроса для вас:

  • Вы абсолютно уверены, что тестируете то, что, по вашему мнению , тестируете?
  • Если кто-то еще посмотрит на модульный тест, смогут ли они выяснить, что должен делать код ?

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

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

Берин Лорич
источник
5

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

Билл Липер
источник
3

В дополнение к другим ответам здесь.

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

Одной из ролей, выполняемых модульным тестированием, является документация.

Пакет модульных тестов - это одно из мест, где можно найти способ использования API.

Те, кто вызывает ваш API, нередко копируют части вашего набора тестов, что приводит к тому, что ваш плохой набор тестов заражает живой код в другом месте.

Пол Бучер
источник
3

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

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

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

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

На этот вопрос я могу только сказать, насколько важно ваше время?

К вашему сведению: заглушки, насмешки и исправления обезьян имеют законное применение. Они только «пахнут», когда их использование не подходит.

dietbuddha
источник
2

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

Морган Херлокер
источник
2

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

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

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

Билл
источник
0

Запахи кода - это только проблема в коде, которая требует изменения или понимания в какой-то момент позже.

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

Ян
источник