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


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

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

11 88

11 ответов:

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

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

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

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

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

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

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

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

однако устранение дублирования обычно является хорошей вещью, если она ничего не скрывает, и устранение дублирования в ваших тестах может привести к лучшему ПРИКЛАДНОЙ ПРОГРАММНЫЙ ИНТЕРФЕЙС. Просто убедитесь, что вы не проходите мимо точки уменьшения отдачи.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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