Практический рефакторинг с использованием модульных тестов

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

Итак, мой первый вопрос: как мне выполнить единичный тест метода в устаревшем коде? Как я могу поместить метод unit test вокруг 500 строк (если мне повезет), который не выполняет только одну задачу? Мне кажется, что мне пришлось бы реорганизовать мой код устаревшего кода, чтобы сделать его работоспособным.

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

Мой второй вопрос несколько сложно объяснить. Вот пример: я хочу реорганизовать унаследованный метод, который заполняет объект из записи базы данных. Разве я не должен был бы написать unit test, который сравнивает объект, полученный с помощью старого метода, с объектом, полученным с использованием моего метода refactored? В противном случае, как я узнаю, что мой рефакторированный метод дает те же результаты, что и старый метод? Если это так, то как долго я оставлю старый устаревший метод в исходном коде? Я просто ударил его после того, как проверил несколько разных записей? Или мне нужно некоторое время поддерживать его на случай, если я столкнусь с ошибкой в ​​моем обновленном коде?

Наконец, поскольку пару человек спросили... старый код был первоначально написан на VB6, а затем перенесен на VB.NET с минимальными изменениями архитектуры.

Ответ 1

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

Мой подход заключается в создании тестов с использованием инструментов Unit Test, которые проверяют множество вещей в одном тесте. В одном тесте я могу проверить подключение к БД, открыть много данных и выполнить проверку до/после БД. Я неизбежно нахожу, что я пишу вспомогательные классы для проверки, и чаще всего эти помощники могут быть добавлены в базу кода, поскольку они инкапсулировали возникающее поведение/логику/требования. Я не имею в виду, что у меня есть один огромный тест, я имею в виду, что тесты mnay выполняют работу, которую пурист назвал бы интеграционным тестом, - такая вещь все еще существует? Также я нашел полезным создать тестовый шаблон, а затем создать множество тестов, чтобы проверить граничные условия, сложную обработку и т.д.

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

Ответ 3

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

Ответ 4

Напишите тесты на любом уровне системы, который вы можете (если можете), если это означает запуск базы данных и т.д., то пусть будет так. Вам нужно будет написать намного больше кода, чтобы утверждать, что этот код в настоящее время выполняет как метод 500 строк +, вероятно, будет иметь много поведения, завершенного в нем. Что касается сравнения старого с новым, если вы пишете тесты против старого кода, они проходят, и они охватывают все, что он делает, когда вы запускаете их против нового кода, который вы эффективно проверяете старым против нового. Я сделал это, чтобы проверить сложный sql-триггер, который я хотел бы реорганизовать, это было больно и потребовалось время, но через месяц, когда мы обнаружили еще одну проблему в этой области, стоило использовать тесты, на которые можно положиться.

Ответ 5

По моему опыту, это реальность при работе над кодом Legacy. Книга (работа с наследием..), упомянутая Esko, - это отличная работа, в которой описываются различные подходы, которые могут вас достать.

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

В стороне, недавняя беседа между Джоэлем С. и Мартином Ф. на TDD/unit-тестах. Я считаю, что важно определить единицу и сосредоточиться на ней! URLs: Открыть письмо, Joel transcript и podcast

Ответ 6

Это действительно одна из ключевых проблем, связанных с попыткой обновить устаревший код. Можете ли вы разбить проблемную область на нечто более зернистое? Способствует ли этот 500 + метод линии ничего, кроме системных вызовов JAR/J32/.NET Framework JARs/DLL/сборок? То есть Есть ли более гранулированные вызовы функций внутри этого 500+ лихтонов линии, которые вы могли бы unit test?

Ответ 7

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

Я нашел его весьма полезным.