Нормально ли тратить столько времени, если не больше, на написание тестов, чем на реальный код?

211

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

Это нормально или я что-то не так делаю?

Вопросы « Стоит ли модульное тестирование или разработка через тестирование? « Мы тратим больше времени на внедрение функционального тестирования, чем на внедрение самой системы, это нормально? И их ответы больше о том, стоит ли тестирование (как в «мы должны вообще пропустить написание тестов?»). Хотя я убежден, что тесты важны, мне интересно, нормально ли я трачу больше времени на тесты, чем реальный код, или это только я.

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

подпружиненным
источник
20
Необычно, но я обнаружил, что трату примерно столько же времени на написание тестов, сколько на написание кода, когда я использую TDD. Это когда я проскакиваю и пишу тесты после того, как потрачу больше времени на тесты, чем на код.
RubberDuck
10
Вы также тратите больше времени на чтение, чем на написание кода.
Турбьерн Равн Андерсен
27
Также тесты являются актуальным кодом. Вы просто не отправляете эту часть клиентам.
Турбьерн Равн Андерсен
5
В идеале вы будете тратить больше времени на выполнение, чем на написание своего кода. (В противном случае, вы просто выполнили бы задачу вручную.)
Джошуа Тейлор
5
@RubberDuck: противоположный опыт здесь. Иногда, когда я пишу тесты по факту, код и дизайн уже довольно аккуратны, поэтому мне не нужно слишком много переписывать код и тесты. Так что написание тестов занимает меньше времени. Это не правило, но это случается со мной довольно часто.
Джорджио

Ответы:

205

Из курса разработки программного обеспечения я помню, что один тратит ~ 10% времени на разработку нового кода, а другие 90% занимаются отладкой, тестированием и документацией.

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

Наконец тесты также должны быть удвоены как документация! Нужно написать юнит-тесты так, как предназначен код; то есть тесты (и использование) должны быть простыми, помещать сложные вещи в реализацию.

Если ваши тесты сложно написать, то код, который они тестируют, вероятно, трудно использовать!

Esoterik
источник
4
Возможно, сейчас самое время разобраться, почему код так сложно тестировать :) Попробуйте "развернуть" сложные многофункциональные строки, которые не являются строго необходимыми, например, массивно вложенные двоичные / троичные операторы ... Я действительно ненавижу ненужные двоичные файлы. / троичный оператор, который также имеет двоичный / троичный оператор в качестве одного из путей ...
Нельсон
53
Прошу не согласиться с последней частью. Если вы стремитесь к очень высокому охвату модульных тестов, вам необходимо охватить случаи использования, которые являются редкими, а иногда и откровенными, против предполагаемого использования вашего кода. Написание тестов для этих ключевых случаев может быть самой трудоемкой частью всей задачи.
От
Я уже говорил об этом в другом месте, но модульное тестирование имеет тенденцию выполняться гораздо дольше, потому что большая часть кода имеет тенденцию следовать своего рода «принципу Парето»: вы можете покрыть около 80% своей логики с помощью около 20% кода, необходимого для покрыть 100% вашей логики (то есть для покрытия всех крайних случаев требуется примерно в пять раз больше кода модульного тестирования). Конечно, в зависимости от инфраструктуры, вы можете инициализировать среду для нескольких тестов, уменьшая общий необходимый код, но даже это требует дополнительного планирования. Достижение 100% уверенности требует гораздо больше времени, чем простое тестирование основных путей.
Phyrfox
2
@phyrfox Я думаю, что это слишком осторожно, это больше похоже на «остальные 99% кода - это крайние случаи». Это означает, что остальные 99% тестов предназначены для этих крайних случаев.
@ Нельсон Я согласен, что вложенные троичные операторы трудно читать, но я не думаю, что они делают тестирование особенно трудным (хороший инструмент покрытия скажет вам, если вы пропустили одну из возможных комбинаций). IMO, программное обеспечение сложно тестировать, когда оно слишком тесно связано или зависит от проводных данных или данных, не переданных в качестве параметра (например, когда условие зависит от текущего времени, а это не передается в качестве параметра). Это не имеет прямого отношения к тому, насколько «читабелен» код, хотя, конечно, при прочих равных условиях читаемый код лучше!
Андрес Ф.
96

Это.

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

Рассмотрим простой код:

public void SayHello(string personName)
{
    if (personName == null) throw new NullArgumentException("personName");

    Console.WriteLine("Hello, {0}!", personName);
}

Какие будут тесты? Здесь есть как минимум четыре простых случая для тестирования:

  1. Имя людей null. Действительно ли выброшено исключение? Это как минимум три строки тестового кода для написания.

  2. Имя людей "Jeff". Мы получаем "Hello, Jeff!"в ответ? Это четыре строки тестового кода.

  3. Имя человека - пустая строка. Какой выход мы ожидаем? Каков фактический результат? Дополнительный вопрос: соответствует ли он функциональным требованиям? Это означает еще четыре строки кода для модульного теста.

  4. Имя человека достаточно короткое для строки, но слишком длинное, чтобы его можно было объединить с "Hello, "восклицательным знаком. Что происходит?

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

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

  • Есть ли дублирование кода в тестах? Тот факт, что это тестовый код, не означает, что код должен дублироваться (копироваться) между аналогичными тестами: такое дублирование усложнит обслуживание этих тестов.

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

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

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

Примечание о TDD

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


¹ Пусть n будет максимальной длиной строки . Мы можем вызвать SayHelloи передать по ссылке строку длины n - 1, которая должна работать очень хорошо. Теперь на Console.WriteLineшаге форматирование должно заканчиваться строкой длиной n + 8, что приведет к исключению. Возможно, из-за ограничений памяти даже строка, содержащая n / 2 символов, приведет к исключению. Вопрос, который следует задать, заключается в том, является ли этот четвертый тест модульным тестом (он похож на него, но может иметь гораздо более высокий эффект с точки зрения ресурсов по сравнению со средними модульными тестами) и проверяет ли он реальный код или базовую структуру.

Арсений Мурзенко
источник
5
Не забывайте, что у человека тоже может быть имя null. stackoverflow.com/questions/4456438/…
psatek
1
@JacobRaihle Я предполагаю, что @MainMa означает, что значение personNameсоответствует a string, но значение personNameплюс объединенные значения переполняется string.
Woz
@JacobRaihle: я отредактировал свой ответ, чтобы объяснить этот момент. Смотрите сноску.
Арсений Мурзенко
4
As a rule of thumb, if you remove a unit test, the branch coverage should decrease.Если я напишу все четыре теста, которые вы упомянули выше, а затем уберу третий тест, уменьшится ли покрытие?
Вивек
3
«достаточно долго» → «слишком долго» (в пункте 4)?
Пауло Эберманн
59

Я думаю, что важно различать два типа стратегий тестирования: модульное тестирование и интеграционное / приемочное тестирование.

Хотя модульное тестирование необходимо в некоторых случаях, оно часто безнадежно переутомлено. Это усугубляется бессмысленными метриками, навязываемыми разработчикам, такими как «100% охват». http://www.rbcs-us.com/documents/Why-Most-Unit-Testing-is-Waste.pdf предоставляет убедительные аргументы для этого. Рассмотрим следующие проблемы с агрессивным юнит-тестированием:

  • Обилие бессмысленных тестов, которые не документируют ценность для бизнеса, а просто существуют, чтобы приблизиться к этому 100% охвату. Там, где я работаю, мы должны писать модульные тесты для фабрик, которые занимаются только созданием новых экземпляров классов. Это не добавляет никакой ценности. Или те длинные методы .equals (), сгенерированные Eclipse - проверять их не нужно.
  • Чтобы упростить тестирование, разработчики подразделяют сложные алгоритмы на меньшие тестируемые блоки. Звучит как победа, верно? Нет, если вам нужно открыть 12 классов, чтобы следовать общему пути кода. В этих случаях модульное тестирование может фактически снизить читаемость кода. Другая проблема заключается в том, что если вы порежете свой код на слишком маленькие кусочки, вы получите множество классов (или кусков кода), которые, кажется, не имеют никакого оправдания, кроме того, что являются подмножеством другого кусочка кода.
  • Рефакторинг высоко coveraged кода может быть трудным, поскольку вы также должны поддерживать обилие модульных тестов , которые зависят от его работы просто так . Это усугубляется поведенческими модульными тестами, где часть вашего теста также проверяет взаимодействие коллабораторов класса (обычно имитируемых).

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

Многие магазины выпили kool-помощь TDD, но, как показывает ссылка выше, ряд исследований показывает, что его польза неубедительна.

firtydank
источник
10
+1 за очко рефакторинга. Раньше я работал над устаревшим продуктом, который был исправлен и работал более десяти лет. Простая попытка определить зависимости для конкретного метода может занять большую часть дня, а затем попытка выяснить, как их высмеивать, может занять еще больше времени. Нередко для пятистрочного изменения требовалось более 200 строк тестового кода, и на его создание уходило больше недели.
TMN
3
Этот. В ответе MainMa тест 4 не должен выполняться (вне академического контекста), потому что подумайте о том, как это будет происходить на практике ... если имя человека близко к максимальному размеру строки, что-то пошло не так. Не тестируйте, в большинстве случаев нет пути к коду для его обнаружения. Соответствующий ответ - позволить фреймворку выбросить основную часть нехватки памяти, потому что именно в этом и заключается проблема.
Móż
3
Я поддерживаю вас до тех пор, пока «не нужно тестировать эти длинные .equals()методы, сгенерированные Eclipse». Я написал тестовый набор для equals()и compareTo() github.com/GlenKPeterson/TestUtils. Практически в каждой реализации, которую я когда-либо тестировал, не хватало. Как вы используете коллекции, если equals()и hashCode()не работаете вместе правильно и эффективно? Я снова болею за ваш остальной ответ и проголосовал за него. Я даже допускаю, что некоторые автоматически сгенерированные методы equals () могут не нуждаться в тестировании, но у меня было так много ошибок с плохими реализациями, что это заставляет меня нервничать.
ГленПетерсон
1
@GlenPeterson Согласен. Для этого мой коллега написал EqualsVerifier. См github.com/jqno/equalsverifier
Tohnmeister
@ Ᶎσᶎ нет, вам все еще нужно проверить неприемлемые данные, вот как люди находят эксплойты безопасности.
gbjbaanb
11

Не может быть обобщено.

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

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

phresnel
источник
Это действительно ценная точка зрения. Вопрос нуждается в большем количестве контекста, чтобы получить полный ответ.
CLF
3

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

Это значит, что:

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

(Это не учитывает рефакторинг кода, который направлен на то, чтобы тратить меньше времени на написание последующего кода. Он уравновешивается рефакторингом тестов, который направлен на то, чтобы тратить меньше времени на написание последующих тестов.)

Да, если вы говорите о написании тестов после факта, вы будете тратить больше времени:

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

Чем вы действительно потратите на написание кода.

Так что да, это ожидаемая мера.

Laurent LA RIZZA
источник
3

Я считаю, что это самая важная часть.

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

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

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

Джесси
источник
2

Это может быть для многих людей, но это зависит.

Если вы пишете тесты в первую очередь (TDD), вы можете испытывать некоторое дублирование во времени, потраченном на написание теста, который на самом деле полезен для написания кода. Рассмотреть возможность:

  • Определение результатов и входных данных (параметров)
  • Соглашения об именах
  • Структура - куда положить вещи.
  • Обычное старое мышление

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

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

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

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

JeffO
источник
2

Нормально ли тратить столько времени, если не больше, на написание тестов, чем на реальный код?

Да, это. С некоторыми оговорками.

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

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

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

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

В предисловии Роя Ошерова « Искусство модульного тестирования» (Мэннинг, 2009) автор признается, что участвовал в проекте, который в значительной степени потерпел неудачу из-за огромного бремени разработки, налагаемого плохо разработанными модульными тестами, которые должны были поддерживаться на протяжении всего длительность разработки. Таким образом, если вы обнаружите, что тратите слишком много времени, не делая ничего, кроме поддержания своих тестов, это не обязательно означает, что вы находитесь на правильном пути, потому что это «нормально». Возможно, ваши усилия по разработке вошли в нездоровый режим, где радикальное переосмысление вашей методики тестирования может быть необходимо для сохранения проекта.

Майк Накис
источник
0

Нормально ли тратить столько времени, если не больше, на написание тестов, чем на реальный код?

  • Да для написания юнит-тестов (тестирование модуля в отдельности), если код сильно связан (унаследовано, недостаточно разделения проблем , отсутствует внедрение зависимостей , не разработан tdd )
  • Да для написания интеграционных / приемочных тестов, если тестируемая логика доступна только через графический код
  • Нет для написания интеграционных / приемочных тестов, пока графический код и бизнес-логика разделены (тест не должен взаимодействовать с графическим интерфейсом)
  • Нет для написания юнит-тестов, если есть разделение проблем, внедрение зависимостей, код был разработан на основе testdriven (tdd)
k3b
источник