Я обычно стараюсь следовать советам книги Эффективная работа с унаследованным Cod е . Я нарушаю зависимости, перемещаю части кода в @VisibleForTesting public static
методы и в новые классы, чтобы сделать код (или, по крайней мере, его часть) тестируемым. И я пишу тесты, чтобы убедиться, что я ничего не нарушаю, когда я изменяю или добавляю новые функции.
Коллега говорит, что я не должен этого делать. Его рассуждения:
- Первоначально исходный код может не работать должным образом. А написание тестов для него затрудняет будущие исправления и модификации, поскольку разработчики должны понимать и модифицировать тесты.
- Если это GUI-код с некоторой логикой (~ 12 строк, 2-3 блока if / else, например), тест не стоит проблем, так как код слишком тривиален для начала.
- Подобные плохие шаблоны могут существовать и в других частях кодовой базы (чего я еще не видел, я довольно новый); будет проще очистить их все в одном большом рефакторинге. Извлечение логики может подорвать эту возможность в будущем.
Следует ли мне избегать извлечения тестируемых частей и написания тестов, если у нас нет времени на полный рефакторинг? Есть ли какой-то недостаток, который я должен рассмотреть?
Ответы:
Вот мое личное ненаучное впечатление: все три причины звучат как широко распространенные, но ложные когнитивные иллюзии.
источник
Несколько мыслей:
При рефакторинге унаследованного кода не имеет значения, если некоторые написанные вами тесты противоречат идеальным спецификациям. Важно то, что они проверяют текущее поведение программы . Рефакторинг - это крошечные изофункциональные шаги, чтобы сделать код чище; Вы не хотите заниматься исправлением ошибок, пока вы рефакторинг. Кроме того, если вы обнаружите явную ошибку, она не будет потеряна. Вы всегда можете написать для него регрессионный тест и временно отключить его или добавить задачу исправления ошибок в свой журнал ожидания для дальнейшего использования. Одна вещь за один раз.
Я бы согласился с тем, что чистый код GUI сложно тестировать и, возможно, он плохо подходит для рефакторинга в стиле «Работаем эффективно ... ». Однако это не означает, что вы не должны извлекать поведение, которое не имеет ничего общего с уровнем графического интерфейса пользователя, и тестировать извлеченный код. И «12 строк, 2-3 блока if / else» не тривиальны. Весь код, имеющий хотя бы немного условной логики, должен быть протестирован.
По моему опыту, большие рефакторинги не легки, и они редко работают. Если вы не ставите перед собой точные, крошечные цели, существует высокий риск того, что вы начнете бесконечную, потрясающую работу, в которой вы никогда не упадете на ноги. Чем больше изменение, тем больше вы рискуете что-то сломать и тем больше у вас будет проблем с поиском того, где вы потерпели неудачу.
Постепенное улучшение ситуации с помощью специальных небольших рефакторингов не «подрывает будущие возможности», а позволяет им - укрепляет заболоченную почву, на которой находится ваше приложение. Вы обязательно должны это сделать.
источник
Также обратите внимание: «Исходный код может не работать должным образом» - это не значит, что вы просто меняете поведение кода, не беспокоясь о последствиях. Другой код может опираться на то, что кажется нарушенным поведением или побочными эффектами текущей реализации. Тестовое покрытие существующего приложения должно упростить рефакторинг позже, потому что это поможет вам узнать, когда вы случайно что-то сломали. Сначала вы должны протестировать наиболее важные детали.
источник
Ответ Килиана охватывает наиболее важные аспекты, но я хочу остановиться на пунктах 1 и 3.
Если разработчик хочет изменить (реорганизовать, расширить, отладить) код, он должен это понять. Она должна убедиться, что ее изменения влияют именно на то поведение, которое она хочет (ничего в случае рефакторинга), и больше ничего.
Если есть тесты, то она должна понимать тесты, конечно. В то же время тесты должны помочь ей понять основной код, а тесты гораздо проще понять, чем функциональный код в любом случае (если только они не являются плохими). И тесты помогают показать, что изменилось в поведении старого кода. Даже если исходный код неверен, и тест проверяет это неправильное поведение, это все равно является преимуществом.
Однако для этого необходимо, чтобы тесты были задокументированы как тестирование существовавшего ранее поведения, а не спецификации.
Также есть некоторые соображения по поводу пункта 3: в дополнение к тому факту, что «большой мах» редко случается, есть еще одна вещь: на самом деле это не так просто. Чтобы было проще, необходимо выполнить несколько условий:
XYZSingleton
? Всегда ли вызывается их экземпляр gettergetInstance()
? И как вы находите свои слишком глубокие иерархии? Как вы ищете ваши объекты бога? Для этого требуется анализ метрик кода, а затем проверка метрик вручную. Или ты просто спотыкаешься о них, когда работаешь, как делал.источник
В некоторых компаниях существует культура, в которой они стараются позволить разработчикам в любое время усовершенствовать код, который напрямую не доставляет дополнительную ценность, например, новые функциональные возможности.
Я, наверное, проповедую здесь обращенным, но это явно ложная экономика. Чистый и лаконичный код выгоден последующим разработчикам. Просто окупаемость не сразу очевидна.
Я лично подписан на Принцип Бойскаута, но другие (как вы видели) этого не делают.
Тем не менее, программное обеспечение страдает от энтропии и создает технический долг. Предыдущие разработчики, испытывающие нехватку времени (или, может быть, просто ленивые или неопытные), могли реализовывать неоптимальные решения с ошибками по сравнению с хорошо разработанными. Хотя может показаться желательным их рефакторинг, вы рискуете внести новые ошибки в то, что в любом случае является (для пользователей) рабочим кодом.
Некоторые изменения имеют меньший риск, чем другие. Например, там, где я работаю, обычно много дублированного кода, который можно безопасно передать в подпрограмму с минимальным воздействием.
В конечном счете, вам необходимо принять решение о том, как далеко вы проведете рефакторинг, но есть бесспорная ценность добавления автоматических тестов, если они еще не существуют.
источник
По моему опыту, тест характеристики какого-то рода работает хорошо. Это дает вам широкий, но не очень специфический охват тестированием относительно быстро, но может быть сложным для реализации для приложений с графическим интерфейсом.
Затем я бы написал модульные тесты для деталей, которые вы хотите изменить, и делаю это каждый раз, когда вы хотите внести изменения, тем самым увеличивая покрытие модульных испытаний с течением времени.
Этот подход дает вам хорошее представление о том, влияют ли изменения на другие части системы, и позволяет вам быстрее вносить необходимые изменения.
источник
Re: «Исходный код может не работать должным образом»:
Тесты не написаны в камне. Они могут быть изменены. И если вы проверили функциональность, которая была неправильной, должно быть легко переписать тест более корректно. В конце концов, должен был быть изменен только ожидаемый результат проверенной функции.
источник
Ну да. Отвечая как инженер по тестированию программного обеспечения. Во-первых, вы должны проверить все, что вы когда-либо делаете. Потому что, если вы этого не сделаете, вы не знаете, работает ли это или нет. Это может показаться очевидным для нас, но у меня есть коллеги, которые видят это по-другому. Даже если ваш проект мал, который, возможно, никогда не будет реализован, вы должны посмотреть в лицо пользователю и сказать, что знаете, что он работает, потому что вы его протестировали.
Нетривиальный код всегда содержит ошибки (цитирование парня из универа; и если в нем нет ошибок, это тривиально), и наша задача состоит в том, чтобы найти их раньше, чем это сделает клиент. Унаследованный код имеет унаследованные ошибки. Если исходный код работает не так, как должен, вы хотите узнать об этом, поверьте мне. С ошибками все в порядке, если вы знаете о них, не бойтесь их искать, для этого и нужны замечания по выпуску.
Если я правильно помню, книга «Рефакторинг» в любом случае говорит, что нужно постоянно тестировать, так что это часть процесса.
источник
У автоматизированного тестирования покрытия.
Остерегайтесь желаемых мыслей, как своих, так и ваших клиентов и начальников. Как бы мне ни хотелось верить, что мои изменения будут правильными в первый раз, и мне придется проверять только один раз, я научился относиться к такого рода мышлению так же, как к нигерийским мошенническим письмам. Ну, в основном; Я никогда не обращался к мошенническим письмам, но недавно (когда орал) я отказался от использования лучших практик. Это был болезненный опыт, который тянул (дорого) снова и снова. Больше никогда!
У меня есть любимая цитата из веб-комикса Freefall: «Вы когда-нибудь работали в сложной области, где у супервизора есть только грубое представление о технических деталях? ... Тогда вы знаете, что самый верный способ заставить вашего супервизора потерпеть неудачу - это следуй за каждым его приказом без вопросов ".
Вероятно, целесообразно ограничить количество времени, которое вы вкладываете.
источник
Если вы имеете дело с большим количеством унаследованного кода, который в настоящее время не тестируется, то получить тестовое покрытие сейчас, а не ждать гипотетического большого переписывания в будущем, - верный шаг. Начинать с написания юнит-тестов нет.
Без автоматического тестирования, после внесения каких-либо изменений в код, вам необходимо выполнить ручное сквозное тестирование приложения, чтобы убедиться, что оно работает. Начните с написания высокоуровневых интеграционных тестов, чтобы заменить это. Если ваше приложение считывает файлы, проверяет их, обрабатывает данные некоторым образом и отображает результаты, которые вы хотите тестировать, которые фиксируют все это.
В идеале вы либо будете иметь данные из плана тестирования вручную, либо сможете получить образец фактических производственных данных для использования. Если нет, то, поскольку приложение находится в производстве, в большинстве случаев оно делает то, что должно быть, поэтому просто составьте данные, которые достигнут всех высших точек, и предположите, что вывод является правильным на данный момент. Это не хуже, чем брать небольшую функцию, предполагая, что она делает то, что называется, или любые комментарии говорят о том, что она должна делать, и писать тесты, предполагая, что она работает правильно.
После того, как вы получите достаточно этих высокоуровневых тестов, написанных для записи нормальной работы приложений и наиболее распространенных случаев ошибок, количество времени, которое вам нужно потратить, стуча по клавиатуре, чтобы попытаться поймать ошибки из кода, выполняя что-то кроме того, что Вы думали, что это должно было значительно упасть, что значительно упростит будущий рефакторинг (или даже большую переписку).
Поскольку вы можете расширить охват модульных тестов, вы можете сократить или даже отказаться от большинства интеграционных тестов. Если ваше приложение читает / записывает файлы или обращается к БД, тестирование этих частей по отдельности и либо их макетирование, либо начало ваших тестов с создания структур данных, считанных из файла / базы данных, являются очевидным местом для начала. На самом деле создание этой инфраструктуры тестирования займет намного больше времени, чем написание набора быстрых и грязных тестов; и каждый раз, когда вы запускаете 2-минутный набор интеграционных тестов, вместо того, чтобы тратить 30 минут на ручное тестирование части того, что охватывало интеграционные тесты, вы уже добиваетесь больших успехов.
источник