Смысл юнит-тестов без TDD


28

У нас стартовал новый (довольно большой) проект, который мы планировали развивать с использованием TDD.

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

Добавлено: Я думаю, что это не дублирует >> этот вопрос << - Я понимаю разницу между UT и TDD. Мой вопрос не о различиях , а о смысле написания юнит-тестов без TDD.


22
Мне любопытно, почему ваш друг обосновывает такую ​​абсурдную позицию ...
Теластин

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

2
Какими будут ваши уровни интеграции? Какими будут ваши юниты? Как часто вы будете проводить рефакторинг ниже каждого уровня тестирования? Как быстро будут выполняться ваши интеграционные тесты? Насколько легко они будут писать? Сколько комбинаторных дел генерируют разные части вашего кода? и т.д ... Если вы не знаете ответы на эти вопросы, то, возможно, еще слишком рано принимать твердые решения. Иногда TDD это здорово. Иногда преимущества менее очевидны. Иногда юнит-тесты необходимы. Иногда приличный набор интеграционных тестов покупает вас почти так же, и они гораздо более гибкие ... Держите ваши варианты открытыми.
topo Восстановить Монику

2
Как некоторые практические советы из опыта, не проверяйте все, если вы не делаете TDD. Определите, какие виды тестов являются ценными. Я обнаружил, что модульные тесты на чистых методах ввода / вывода чрезвычайно полезны, в то время как интеграционные тесты на очень высоком уровне приложения (например, отправка веб-запросов в веб-приложении) также чрезвычайно полезны. Следите за интеграционными тестами среднего уровня и модульными тестами, которые требуют большой настройки. Также посмотрите это видео: youtube.com/watch?v=R9FOchgTtLM .
jpmc26

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

Ответы:


52

TDD используется главным образом (1) для обеспечения покрытия, (2) и для обеспечения поддерживаемой, понятной, проверяемой конструкции. Если вы не используете TDD, вы не получите гарантированное покрытие кода. Но это ни в коем случае не означает, что вы должны отказаться от этой цели и беспечно жить с 0% охватом.

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


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

5
@DougM - В идеальном мире может быть ...
Теластин

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

17
TDD не гарантирует покрытие кода. Это опасное предположение. Вы можете кодировать против тестов, проходить эти тесты, но все же есть крайние случаи
Роберт Харви

4
@MickyDuncan Я не совсем уверен, что полностью понимаю вашу озабоченность. Пересмешка - это совершенно приемлемый метод, используемый для изоляции одного компонента от других, так что тесты поведения этого компонента могут выполняться независимо. Да, доведенный до крайности, это может привести к чрезмерно спроектированному программному обеспечению, но может быть применен и любой метод разработки, если он используется ненадлежащим образом. Кроме того, как указывает DHH в статье, которую вы цитируете, идея использовать когда-либо только полные системные тесты столь же плоха, если не на самом деле хуже. Важно использовать суждение, чтобы решить, каков наилучший способ тестирования какой-либо конкретной функции .
Жюль

21

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

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

Мне нужна была одна конкретная структура данных, которая позволяла бы мне отслеживать произвольное количество битов. Поскольку проект написан на Java, я выбрал Java BitSetи немного его изменил (мне также требовалась возможность отслеживать ведущие 0, чего Java BitSet по какой-то причине не делает .....). После достижения охвата ~ 93% я начал писать некоторые тесты, которые фактически подчеркивали бы написанную мной систему. Мне нужно было сравнить некоторые аспекты функциональности, чтобы убедиться, что они будут достаточно быстрыми для моих конечных требований. Неудивительно, что одна из функций, которые я переопределил в BitSetинтерфейсе, была абсурдно медленной при работе с большими наборами битов (в данном случае это сотни миллионов битов). Другие переопределенные функции основывались на этой единственной функции, поэтому это была огромная горлышко бутылки.

В итоге я пошел к чертежной доске и нашел способ манипулировать базовой структурой BitSet, которая является long[]. Я разработал алгоритм, попросил коллег ввести их, а затем приступил к написанию кода. Затем я запустил юнит-тесты. Некоторые из них сломались, и те, которые действительно указали мне, где я должен был искать в своем алгоритме, чтобы исправить это. После исправления всех ошибок модульных тестов я смог сказать, что функция работает как надо. По крайней мере, я мог быть уверен, что этот новый алгоритм работает так же, как и предыдущий алгоритм.

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

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

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


7

Вы можете разбить код примерно на 4 категории:

  1. Просто и редко меняется.
  2. Просто и часто меняется.
  3. Сложный и редко меняется.
  4. Сложный и часто меняется.

Модульные тесты становятся более ценными (вероятно, для выявления важных ошибок) по мере продвижения по списку. В моих личных проектах я почти всегда делаю TDD в категории 4. В категории 3 я обычно делаю TDD, если ручное тестирование не является более простым и быстрым. Например, код сглаживания будет сложным для написания, но гораздо проще проверить визуально, чем писать модульный тест, поэтому модульный тест будет стоить мне, только если этот код часто меняется. Остальную часть моего кода я проверяю только после того, как обнаружу ошибку в этой функции.

Иногда сложно заранее узнать, к какой категории относится определенный блок кода. Ценность TDD в том, что вы случайно не пропускаете ни одного из сложных модульных тестов. Стоимость TDD - это все время, которое вы тратите на написание простых модульных тестов. Однако обычно люди, имеющие опыт работы с проектом, с достаточной степенью уверенности знают, к какой категории относятся различные части кода. Если вы не занимаетесь TDD, вы должны хотя бы попытаться написать наиболее ценные тесты.


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

1

Будь то юнит, компонент, интеграционные или приемочные испытания, важная часть - это то, что он должен быть автоматизирован Отсутствие автоматических тестов является фатальной ошибкой для любого программного обеспечения, от простых CRUD до самых сложных вычислений. Причина заключается в том, что написание автоматических тестов всегда будет стоить меньше, чем постоянная потребность в запуске всех тестов вручную, когда вы этого не делаете, на порядки. После того, как вы их написали, вам просто нужно нажать кнопку, чтобы посмотреть, пройдут они или нет. Ручные тесты всегда будут длиться долго и зависят от людей (живых существ, которым скучно, может не хватать внимания и т. Д.), Чтобы иметь возможность проверить, пройдены ли тесты успешно или нет. Короче говоря, всегда пишите автоматизированные тесты.

Теперь о причине, по которой ваш коллега может быть против проведения любого вида модульного тестирования без TDD: возможно, потому, что сложнее доверять тестам, написанным после производственного кода. И если вы не можете доверять своим автоматизированным тестам, они ничего не стоят . После цикла TDD вы должны сначала сделать тест неудачным (по правильной причине), чтобы иметь возможность написать производственный код, чтобы он прошел (и не более). Этот процесс по сути проверяет ваши тесты, поэтому вы можете им доверять. Не говоря уже о том, что написание тестов перед фактическим кодом подталкивает вас к разработке модулей и компонентов, чтобы их было легче тестировать (высокий уровень развязки, применение SRP и т. Д.). Хотя, конечно, выполнение TDD требует дисциплины .

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

Это означает, что вы должны найти другой способ проверить свои тесты , как это делает TDD. Поэтому вы прибегаете к отладке, комментированию частей производственного кода и т. Д., Чтобы иметь возможность доверять тестам. Проблема в том, что процесс «тестирования ваших тестов» намного медленнее. Добавление этого времени к тому времени, которое вы потратите на запуск специальных тестов вручную (поскольку у вас нет автоматических тестов, пока вы кодируете рабочий код), по моему опыту, приводит к общему процессу, который намного медленнее, чем практика ТДД "По книге" (Кент Бек - ТДД по примеру). Кроме того, я готов сделать ставку и сказать, что «тестирование ваших тестов» после их написания требует гораздо больше дисциплины, чем TDD.

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


0

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

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


0

На самом деле дядя Боб упомянул очень интересный момент в одном из своих видео «Чистые кодеры». Он сказал, что цикл Red-Green-Refactor можно применять двумя способами.

1-й - это обычный способ TDD. Напишите провальный тест, затем пройдите тест и, наконец, выполните рефакторинг.

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

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

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


«... идея состоит в том, чтобы пойти очень маленькими шагами и немедленно протестировать только что добавленный фрагмент производственного кода». Я не согласен. То, что вы описываете, хорошо, когда у вас уже есть четкое представление о том, что вы хотите сделать, и вы хотите поработать над деталями, но сначала вам нужно получить общую картину. В противном случае, делая очень маленькие шаги (тестирование, разработка, тестирование, развитие), вы можете потеряться в деталях. «Если вы не знаете, куда идете, вы можете туда не попасть».
Джорджио
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.