Является ли дублированный код более переносимым в модульных тестах?

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

Согласны ли вы, что этот компромисс существует? Если да, предпочитаете ли вы, чтобы ваши тесты были читабельными или поддерживаемыми?

Ответ 1

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

Если дублирование установлено в настройке, рассмотрите возможность использования метода setUp или более (или более гибкого) Способы создания.

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

Если дублирование в утверждениях, возможно, вам понадобится Пользовательские утверждения. Например, если несколько тестов имеют строку утверждений типа:

assertEqual('Joe', person.getFirstName())
assertEqual('Bloggs', person.getLastName())
assertEqual(23, person.getAge())

Тогда, возможно, вам понадобится один метод assertPersonEqual, чтобы вы могли написать assertPersonEqual(Person('Joe', 'Bloggs', 23), person). (Или, возможно, вам просто нужно перегрузить оператор равенства на Person.)

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

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

Ответ 2

Читаемость более важна для тестов. Если тест не удался, вы хотите, чтобы проблема была очевидной. Разработчику не нужно пробираться через много сильно обработанного тестового кода, чтобы точно определить, что не удалось. Вы не хотите, чтобы ваш тестовый код стал настолько сложным, что вам нужно написать unit-test-tests.

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

Ответ 3

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

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

С другой стороны, тестовый код должен поддерживать уровень дублирования. Дублирование в тестовом коде достигает двух целей:

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

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

Когда дублирование выполняется в части проверки "проверки", часто бывает полезно определить настраиваемые методы утверждения. Конечно, эти методы должны все еще проверять четко определенное отношение, которое может быть показано в имени метода: assertPegFitsInHole → good, assertPegIsGood → bad.

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

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

Ответ 4

Я согласен. Компромисс существует, но отличается в разных местах.

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

Ответ 5

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

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

Ответ 6

Я ЛЮБЛЮ rspec из-за этого:

У этого есть 2 вещи, чтобы помочь -

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

  • вложенные контексты.
    вы можете по существу иметь метод "setup" и "teardown" для определенного подмножества ваших тестов, а не только для каждого в классе.

Чем раньше .NET/Java/другие тестовые среды применяют эти методы, тем лучше (или вы можете использовать IronRuby или JRuby для написания ваших тестов, что, на мой взгляд, является лучшим вариантом)

Ответ 7

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

Ответ 8

В идеале, модульные тесты не должны сильно меняться после их написания, поэтому я буду склоняться к удобочитаемости.

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

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

Ответ 9

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

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

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

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

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

Ответ 10

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

Ответ 11

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

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

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