Я рефакторинг огромного унаследованного кода класса. Рефакторинг (я полагаю) защищает это:
- написать тесты для унаследованного класса
- рефакторинг, черт возьми, из класса
Проблема: после того, как я проведу рефакторинг класса, мои тесты на шаге 1 нужно будет изменить. Например, то, что раньше было в унаследованном методе, теперь может быть отдельным классом. То, что было одним методом, теперь может быть несколькими методами. Весь ландшафт унаследованного класса может быть стерт с лица земли во что-то новое, и поэтому тесты, которые я напишу на шаге 1, будут практически недействительными. По сути, я буду добавлять Шаг 3. переписать мои тесты обильно
Какова цель тогда писать тесты перед рефакторингом? Это больше похоже на академическое упражнение по созданию большей работы для себя. Сейчас я пишу тесты для метода и узнаю больше о том, как тестировать вещи и как работает старый метод. Это можно узнать, просто прочитав сам унаследованный код, но написание тестов - это почти то же самое, что потирать нос, а также документировать эти временные знания в отдельных тестах. Таким образом, у меня почти нет выбора, кроме как узнать, что делает код. Я сказал здесь временно, потому что я буду рефакторировать чертовски код, и вся моя документация и тесты в значительной степени будут недействительными, за исключением того, что мои знания останутся и позволят мне быть более свежим в рефакторинге.
Это реальная причина писать тесты перед рефакторингом - чтобы помочь мне лучше понять код? Должна быть другая причина!
Пожалуйста, объясни!
Замечания:
Есть такая запись: есть ли смысл писать тесты для унаследованного кода, когда нет времени для полного рефакторинга? но там написано «написать тесты перед рефакторингом», но не сказано «почему» или что делать, если «написание тестов» кажется «занятой работой, которая скоро будет уничтожена»
источник
Ответы:
Рефакторинг - это очистка фрагмента кода (например, улучшение стиля, дизайна или алгоритмов) без изменения (видимого извне) поведения. Вы пишете тесты , чтобы не убедиться , что код до и после рефакторинга это то же самое, вместо того, чтобы писать тесты , как показатель того, что ваше приложение до и после рефакторинга ведет себя тот же: новый код совместим, и не были введены никакие новые ошибки.
Ваша основная задача должна заключаться в написании модульных тестов для открытого интерфейса вашего программного обеспечения. Этот интерфейс не должен изменяться, поэтому тесты (которые являются автоматической проверкой этого интерфейса) также не должны меняться.
Тем не менее, тесты также полезны для поиска ошибок, поэтому имеет смысл написать тесты и для приватных частей вашего программного обеспечения. Ожидается, что эти тесты будут меняться в течение всего процесса рефакторинга. Если вы хотите изменить детали реализации (например, присвоение имен частной функции), сначала обновите тесты, чтобы отразить ваши измененные ожидания, затем убедитесь, что тест не пройден (ваши ожидания не оправдались), а затем измените фактический код. и проверьте, что все тесты прошли снова. Ни в коем случае тесты для открытого интерфейса не должны начинать проваливаться.
Это сложнее при выполнении изменений в большем масштабе, например, при перепроектировании нескольких частей, зависящих от кода. Но будет какая-то граница, и на этой границе вы сможете писать тесты.
источник
Ах, поддержание устаревших систем.
В идеале ваши тесты обрабатывают класс только через его интерфейс с остальной частью кода, другими системами и / или пользовательским интерфейсом. Интерфейсы. Вы не можете реорганизовать интерфейс, не затрагивая эти компоненты, расположенные выше или ниже. Если это все один тесно связанный беспорядок, то вы могли бы также рассмотреть переписывание, а не рефакторинг, но это в значительной степени семантика.
Редактировать: скажем, часть вашего кода измеряет что-то, и у него есть функция, которая просто возвращает значение. Единственный интерфейс - это вызов функции / метода / whatnot и получение возвращаемого значения. Это слабое соединение и его легко тестировать. Если ваша основная программа имеет подкомпонент, который управляет буфером, и все обращения к нему зависят от самого буфера, некоторых управляющих переменных, и она возвращает сообщения об ошибках через другой раздел кода, то вы можете сказать, что он тесно связан, и это трудно для модульного теста. Вы все еще можете сделать это с достаточным количеством поддельных объектов и еще много чего, но это становится грязным. Особенно в ц. Любое количество рефакторинга, как работает буфер, сломает подкомпонент.
Конец Править
Если вы тестируете свой класс с помощью интерфейсов, которые остаются стабильными, то ваши тесты должны быть действительными до и после рефакторинга. Это позволяет вам вносить изменения с уверенностью, что вы не сломали его. По крайней мере, больше уверенности.
Это также позволяет вам делать постепенные изменения. Если это большой проект, я не думаю, что вам захочется просто разрушить его, создать совершенно новую систему и начать разработку тестов. Вы можете изменить одну его часть, протестировать ее и убедиться, что изменения не повредят остальную часть системы. Или, если это произойдет, вы можете, по крайней мере, увидеть, как развивается гигантский запутанный беспорядок, а не удивляться этому, когда выпускаете.
Хотя вы можете разделить метод на три, они все равно будут делать то же, что и предыдущий метод, поэтому вы можете пройти тест для старого метода и разделить его на три. Усилия по написанию первого теста не пропали даром.
Кроме того, трактовка знаний унаследованной системы как «временных знаний» не будет успешной. Знание того, как раньше это делалось, жизненно важно, когда речь идет об устаревших системах. Крайне полезен для векового вопроса "почему, черт возьми, он это делает?"
источник
Мой собственный ответ / реализация:
От исправления различных ошибок во время рефакторинга я понимаю, что я бы не делал код так легко, не имея тестов. Тесты предупреждают меня о поведенческих / функциональных «различиях», которые я представляю, изменяя свой код.
Вам не нужно быть чрезмерно осведомленным, когда у вас есть хорошие тесты на месте. Вы можете редактировать свой код в более спокойной манере. Тесты проводят проверки и проверки работоспособности для вас.
Кроме того, мои тесты остались почти такими же, как я рефакторинг и не были уничтожены. На самом деле, я заметил некоторые дополнительные возможности добавления утверждений в мои тесты, когда углублялся в код.
ОБНОВИТЬ
Что ж, теперь я сильно меняю свои тесты: / Поскольку я реорганизовал исходную функцию (удалил функцию и вместо этого создал новый класс чище, переместив пух, который раньше был внутри функции, за пределы нового класса), так что теперь тестируемый код, который я запускал ранее, принимает разные параметры под другим именем класса и дает разные результаты (исходный код с брезгом имел больше результатов для тестирования). И поэтому мои тесты должны отражать эти изменения, и в основном я переписываю свои тесты во что-то новое.
Я предполагаю, что есть другие решения, которые я могу сделать, чтобы избежать переписывания тестов. т.е. сохранить старое имя функции с новым кодом и пух внутри него ... но я не знаю, является ли это лучшей идеей, и у меня пока нет такого большого опыта, чтобы судить, что делать.
источник
Используйте ваши тесты для управления вашим кодом, как вы это делаете. В устаревшем коде это означает написание тестов для кода, который вы собираетесь изменить. Таким образом, они не являются отдельным артефактом. Тесты должны быть о том, чего должен достичь код, а не о внутреннем понимании того, как он это делает.
Как правило, вы хотите добавить тесты для кода, в котором его нет), для кода, который вы собираетесь реорганизовать, чтобы убедиться, что поведение кодов продолжает работать, как ожидалось. Таким образом, непрерывный запуск тестового набора во время рефакторинга - это фантастическая сеть безопасности. Мысль об изменении кода без набора тестов, чтобы подтвердить, что изменения не влияют на что-то непредвиденное, пугающая.
Что касается мелочей обновления старых тестов, написания новых тестов, удаления старых тестов и т. Д., Я просто рассматриваю это как часть стоимости современной профессиональной разработки программного обеспечения.
источник
Какова цель рефакторинга в вашем конкретном случае?
Предположим, что в моем согласии с моим ответом мы все (до некоторой степени) верим в TDD (разработка через тестирование).
Если целью вашего рефакторинга является очистка существующего кода без изменения существующего поведения, то при написании тестов перед рефакторингом вы гарантируете, что не изменили поведение кода, если вы добьетесь успеха, тогда тесты будут успешными как до, так и после Вы рефакторинг.
Тесты помогут вам убедиться, что ваша новая работа действительно работает.
Тесты, вероятно, также выявят случаи, когда оригинальная работа не работает.
Но как вы на самом деле делаете какой-либо значительный рефакторинг, не влияя до некоторой степени на поведение ?
Вот краткий список нескольких вещей, которые могут произойти во время рефакторинга:
Я собираюсь доказать, что каждое из перечисленных действий каким-то образом меняет поведение .
И я собираюсь доказать, что если ваш рефакторинг изменит поведение, ваши тесты все равно будут такими, какими вы убедитесь, что ничего не сломали.
Может быть, поведение не меняется на макроуровне, но цель модульного тестирования не в том, чтобы гарантировать поведение макроса. Это интеграционное тестирование. Цель модульного тестирования - убедиться, что отдельные кусочки, из которых вы строите свой продукт, не сломаны. Цепочка, самое слабое звено и т. Д.
Как насчет этого сценария:
Предположим, у вас есть
function bar()
function foo()
звонитbar()
function flee()
также делает вызов функцииbar()
Просто для разнообразия,
flam()
звонитfoo()
Все работает великолепно (по-видимому, по крайней мере).
Вы рефакторинг ...
bar()
переименован вbarista()
flee()
изменено на вызовbarista()
foo()
это не изменено на вызовbarista()
Очевидно, ваши тесты для обоих
foo()
иflam()
сейчас провалились.Может быть, вы не поняли,
foo()
позвонилbar()
в первую очередь. Вы, конечно, не понимали, чтоflam()
зависитbar()
от способаfoo()
.Без разницы. Суть в том, что ваши тесты обнаружат недавно нарушенное поведение обоих
foo()
иflam()
, в пошаговом порядке, во время вашей работы по рефакторингу.Тесты в итоге помогут вам хорошо провести рефакторинг.
Если у вас нет никаких тестов.
Это немного надуманный пример. Есть те, кто будет утверждать, что если бы смена
bar()
перерывовfoo()
, тоfoo()
была слишком сложной для начала и должна быть разбита. Но процедуры могут вызывать другие процедуры по причине, и невозможно устранить всю сложность, верно? Наша задача - справляться со сложностью достаточно хорошо.Рассмотрим другой сценарий.
Вы строите здание.
Вы строите леса, чтобы гарантировать, что здание построено правильно.
Строительные леса, среди прочего, помогут вам построить шахту лифта. После этого вы сносите леса, но шахта лифта остается. Вы уничтожили «оригинальную работу», уничтожив леса.
Аналогия незначительна, но суть в том, что не случайно создать инструменты, которые помогут вам создать продукт. Даже если инструменты не являются постоянными, они полезны (даже необходимы). Плотники постоянно делают джиг, иногда только на одну работу. Затем они разрывают пазухи, иногда используя детали, чтобы построить другие пазухи для других работ, иногда нет. Но это не делает джиг бесполезным или напрасным усилием.
источник