Является ли дублированный код более приемлемым в модульных тестах? - PullRequest
97 голосов
/ 25 сентября 2008

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

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

Ответы [ 11 ]

163 голосов
/ 25 сентября 2008

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

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

63 голосов
/ 27 сентября 2008

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

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

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

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

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

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

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

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

37 голосов
/ 25 сентября 2008

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

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

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

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

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

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

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

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

8 голосов
/ 25 сентября 2008

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

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

7 голосов
/ 25 сентября 2008

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

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

4 голосов
/ 28 сентября 2008

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

3 голосов
/ 25 сентября 2008

Я люблю rspec из-за этого:

В нем есть 2 вещи, которые могут помочь -

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

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

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

2 голосов
/ 03 мая 2015

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

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

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

2 голосов
/ 25 сентября 2008

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

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

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

2 голосов
/ 25 сентября 2008

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

...