Практический рефакторинг с использованием юнит-тестов - PullRequest
11 голосов
/ 07 февраля 2009

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

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

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

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

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

Ответы [ 7 ]

9 голосов
/ 07 февраля 2009

Для получения инструкций по рефакторингу унаследованного кода вы можете прочитать книгу Эффективная работа с унаследованным кодом . Также доступна короткая версия PDF здесь .

4 голосов
/ 07 февраля 2009

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

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

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

1 голос
/ 07 февраля 2009

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

Я видел похожие проблемы с самим модульным тестом, который превратился в системный / функциональный тест. Наиболее важной вещью для разработки тестов для Legacy или существующего кода является определение термина «модуль». Это может быть даже функциональная единица, такая как «чтение из базы данных» и т. Д. Идентифицируйте ключевые функциональные единицы и поддерживайте тесты, которые увеличивают ценность.

Кроме того, недавно Джоэл С. и Мартин Ф. говорили о TDD / юнит-тестах. Я считаю, что важно определить единицу и сосредоточиться на ней! URL: Открытое письмо , Стенограмма Джоэла и подкаст

1 голос
/ 07 февраля 2009

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

1 голос
/ 07 февраля 2009

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

0 голосов
/ 28 мая 2010

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

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

0 голосов
/ 07 февраля 2009

Это действительно одна из ключевых проблем при попытке обновить устаревший код. Вы можете разбить проблемную область на что-то более гранулярное? Делает ли этот метод с 500+ строками что-то кроме системных вызовов JAR / DLL / сборок / сборок JDK / Win32 / .NET Framework? То есть Есть ли более гранулированные вызовы функций в этом 500+ линейном гиганте, которые вы могли бы тестировать модулем?

...