Существуют ли ситуации, когда модульные тесты вредны для кода?
Большинство обсуждений на этом сайте очень позитивно относятся к модульному тестированию. Я поклонник единицы тестирования себя. Тем не менее, я нашел, что обширное модульное тестирование приносит свои собственные проблемы. Например, модульные тесты часто тесно связаны с тестируемым кодом, что может сделать API более дорогостоящим, поскольку объем тестов растет.
Вы нашли реальные ситуации, когда модульные тесты вредны для качества кода или времени доставки? Как вы справлялись с этими ситуациями? Существуют ли какие-либо "лучшие практики", которые могут быть применены к разработке и внедрению модульных тестов?
Здесь есть несколько смежный вопрос: Почему в вашем проекте не было тестирования модулей?
Ответы
Ответ 1
При обширном модульном тестировании вы начнете обнаруживать, что операции по рефакторингу более дороги по точно указанным причинам.
ИМХО, это хорошо. Дорогие и большие изменения в API должны иметь большую стоимость по сравнению с небольшими и дешевыми изменениями. Рефакторинг не является бесплатной операцией, и важно понимать влияние как на себя, так и на потребителей вашего API. Unit Tests - отличная линейка для измерения того, насколько дорого будет использоваться API-интерфейс.
Частично эта проблема устраняется с помощью инструментария. Большинство IDE прямо или косвенно (через плагины) поддерживают операции рефакторинга в своей базе кода. Использование этих операций для изменения модульных тестов облегчит боль.
Ответ 2
Есть ли "лучшие практики", которые могут применяться к дизайну и внедрение модульных тестов?
Убедитесь, что тесты вашего устройства не стали интеграционными тестами. Например, если у вас есть модульные тесты для класса Foo, то в идеале тесты могут только разорваться, если
- произошла смена Foo
- или произошли изменения в интерфейсах, используемых Foo
- или произошла смена модели домена (обычно у вас будут такие классы, как "
Customer
", которые являются центральными для проблемного пространства, не имеют места для абстракции и, следовательно, не скрываются за интерфейсом)
Если ваши тесты терпят неудачу из-за каких-либо других изменений, то они стали интеграционными тестами, и у вас появятся проблемы с ростом системы. Модульные тесты не должны иметь таких проблем с масштабируемостью, поскольку они проверяют изолированный блок кода.
Ответ 3
Один из проектов, над которыми я работал, был сильно протестирован; у нас было более 1000 единичных тестов для 20 классов. Был немного больше тестового кода, чем производственный код. В модульных тестах были обнаружены многочисленные ошибки, введенные во время операций рефакторинга; они определенно упростили и безопасно вносили изменения, расширяли функциональность и т.д. Выпущенный код имел очень низкий уровень ошибок.
Чтобы поощрять себя к написанию модульных тестов, мы специально решили сохранить их "быстро и грязно" - мы проверили бы bash тест, когда мы подготовили код проекта, а тесты были скучными и "не реальным кодом", поэтому, как только мы написали книгу, в которой реализована функциональность производственного кода, мы сделали и двинулись дальше. Единственным критерием для тестового кода было то, что он полностью реализовал API производственного кода.
То, что мы усвоили, заключается в том, что этот подход не масштабируется. По мере развития кода мы увидели необходимость изменения схемы связи между нашими объектами, и вдруг у меня было 600 неудачных модульных тестов! У меня это заняло несколько дней. Этот уровень тестового поломки произошел два или три раза с дальнейшими крупными архитектурными рефакторингами. В каждом случае я не считаю, что мы могли бы разумно предвидеть эволюцию кода, которая требовалась заранее.
Мораль этой истории для меня такова: код модульного тестирования должен быть таким же чистым, как и производственный код. Вы просто не можете уйти с резкой и склеиванием в модульных тестах. Вам необходимо применить разумный рефакторинг и отделить тесты от производственного кода, где это возможно, с помощью прокси-объектов.
Конечно, все это добавляет сложности и стоимости ваших модульных тестов (и может вводить ошибки в ваши тесты!), так что это прекрасный баланс. Но я верю, что концепция "единичных тестов", взятая в изоляции, - это не ясная и однозначная победа, которую она часто делала. Мой опыт заключается в том, что модульные тесты, как и все остальное в программировании, требуют ухода и не являются методологией, которая может применяться вслепую. Поэтому мне удивительно, что я больше не обсуждал эту тему на форумах, подобных этой и в литературе.
Ответ 4
Превышение ложных срабатываний может замедлить развитие, поэтому важно проверить, что вы на самом деле хотите оставаться инвариантным. Обычно это означает, что для проверки требований необходимо заранее выполнить блок-тесты, а затем выполнить более подробные модульные тесты для обнаружения неожиданных сдвигов в выходе.
Ответ 5
В основном в тех случаях, когда система была разработана без модульного тестирования, это было запоздалой мыслью, а не инструментом проектирования. Когда вы разрабатываете автоматизированные тесты, вероятность нарушения вашего API уменьшается.
Ответ 6
Я думаю, вы смотрите на фиксацию симптома, а не на распознавание всей проблемы. Коренная проблема заключается в том, что истинный API является опубликованным интерфейсом *, и он должен обладать теми же границами, которые вы бы разместили на любом контракте на программирование: никаких изменений! Вы можете добавить в API и назвать его API v2, но вы не можете вернуться и изменить API v1.0, иначе вы действительно нарушите обратную совместимость, что почти всегда плохо для API.
(* Я не хочу вызывать какую-либо конкретную технологию интерфейса или язык, интерфейс может означать что-либо из объявлений класса вверх.)
Я бы предположил, что подход, основанный на тестировании, помог бы предотвратить многие из этих проблем в первую очередь. С TDD вы будете "чувствовать" неловкость интерфейсов во время написания тестов, и вам придется исправить эти интерфейсы ранее в этом процессе, а не ждать, пока вы не написали тысячу тестов.
Одним из основных преимуществ Test Driven Development является то, что он дает вам мгновенную обратную связь о программном использовании вашего класса/интерфейса. Акт написания теста - это тест вашего проекта, в то время как акт запуска теста - это тест вашего поведения. Если сложно или неудобно писать тест для определенного метода, то этот метод, скорее всего, будет использоваться неправильно, что означает его слабую конструкцию, и его нужно быстро реорганизовать.
Ответ 7
Если вы уверены, что ваш код не будет использоваться повторно, вам не понадобится поддерживать ваш проект, простой и очень короткий срок; то вам не нужны модульные тесты.
Модульные тесты полезны для облегчения изменений и обслуживания. Они действительно добавляют немного времени для доставки, но оплачиваются в среднесрочной/долгосрочной перспективе. Если нет средних/длительных сроков, это может быть ненужным, поскольку тесты вручную достаточно.
Но все это очень маловероятно. Таким образом, они все еще являются трендом:)
Кроме того, иногда может быть необходимым бизнес-решение вкладывать меньше времени в тестирование, чтобы иметь более быструю срочную доставку (которую нужно будет оплатить с интересом позже)
Ответ 8
Да, есть ситуации, когда модульное тестирование может нанести ущерб качеству кода и времени доставки. Если вы создадите слишком много unit test, ваш код будет искажен интерфейсами, и ваше качество кода в целом пострадает. Абстракция велика, но вы можете иметь слишком много.
Если ваш блок письма проверяет прототип или систему, которая имеет большие шансы на серьезные изменения, ваш unit test будет влиять на время доставки. В этих случаях часто лучше писать приемочные испытания, которые проверяют ближе к концу.
Ответ 9
Медленные модульные тесты часто могут нанести ущерб развитию. Обычно это происходит, когда модульные тесты становятся интеграционными тестами, которые необходимо ударить по веб-службам или базе данных. Если ваш комплект модульных тестов занимает более часа, чтобы бегать, часто вы обнаружите, что ваша команда по сути парализована в течение этого часа, ожидая, пройдет ли тестирование единицы или нет (поскольку вы не хотите продолжать строить сломанный фундамент).
С учетом сказанного, я думаю, что преимущества намного перевешивают недостатки во всех, кроме самых надуманных случаях.