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

Что такое рефакторинг?

Если обобщить, под рефакторингом понимают реорганизацию кода без изменения его поведения. То есть, мы ставим во главу угла поведение: нам нужно гарантировать, что, выполняя рефакторинг, мы его не изменим.
Адская задачка: приступаем к рефакторингу унаследованного кода  - 1
Как можно это обеспечить? Ответ прост: тесты. Именно тесты подтверждают, что наша система или компонент ведет себя так, как ожидалось, при заданных условиях. При наличии тестов, процесс рефакторинга существенно упрощается, поскольку у нас есть средство проверки (валидации). Но код, хорошо покрытый тестами — это идеальный случай, и в реальности встречается редко. Давайте взглянем на случай более сложный и реалистичный.

Оживший кошмар по имени «Legacy-проекты»

Рассмотрим некий сценарий из жизни рядового (или не очень) программиста. Представьте, что менеджер бросает вас на проект с legacy-кодом (то есть, унаследованным, не очень новым и чаще всего — весьма запутанным кодом). Никто не в курсе, как этот код работает, документации катастрофически не хватает, а ту, что в наличии, писал неведомый косноязычный иностранец, пожелавший остаться неизвестным…
Адская задачка: приступаем к рефакторингу унаследованного кода  - 2
Что теперь делать? Не увольняться же, в конце концов! Хотя после пары-тройки часов копания в дебрях legacy, этот вариант покажется вам заманчивым. Но сдаваться рано! На самом деле всё, что от нас требуется – обеспечить неизменность поведения. В условиях недостатка информации это — задача нетривиальная. С чего же начать? Писать тесты. Тесты, тесты, и ещё раз — тесты.

Шаг 1. «Дымовые» тесты

Первый тип тестов, которым мы воспользуемся, – это так называемые «дымовые» тесты (англ. smoke testing). Под этим термином понимают минимальный набор тестов на явные ошибки. «Дымовой» тест обычно выполняется самим программистом. Не проходившую этот тест программу не имеет смысла отдавать на более глубокое тестирование. «Дымовые» тесты отлично подходят для демонстрации того, что наиболее важная часть системы ведет себя адекватно и предсказуемо. То есть мы добавляем "дымовые" тесты в наш конвейер развертывания (создайте его, если еще не создали) на шаге проверки сборки. После выполнения "дымовых" тестов мы можем быть уверены, что ничего не нарушили критично. Кроме того, в довесок к этому чудесному эффекту мы получаем знания: после создания "дымовых" тестов, мы начинаем гораздо лучше понимать систему.

Шаг 2. Модульное тестирование

Наша следующая стратегия тестирования – модульное тестирование. Модульное или юнит-тестирование (англ. unit testing) — процесс в программировании, позволяющий проверить на корректность отдельные модули исходного кода программы. Идея состоит в том, чтобы писать тесты для каждой нетривиальной функции или метода. Этот подход используется в Java-программировании повсеместно. Реализация модульного тестирования – намного более сложная задача. Этот процесс должен быть пошаговым. Невозможно написать модульные тесты для каждого компонента, да и смысла в этом особого нет, поскольку вам всё равно вскоре предстоит рефакторинг. Следовательно, нужно убедиться, что хотя бы основные компоненты ведут себя как положено. Итак, выберите один из основных компонентов и начните его тестировать. Написание модульных тестов автоматически приведет к небольшому рефакторингу унаследованного кода. Вдобавок, вы начнете понимать внутреннее устройство проекта гораздо лучше.

Итоги

После реализации этих стратегий, у вас не останется не протестированного кода, и туман легаси-кода станет гораздо менее плотным. Разумеется, в команде может найтись человек, который скажет, что на тестирование нет времени. Постарайтесь убедить его, что напротив, отказ от тестирования приведет к гораздо (гораздо!) большим временным потерям в долгосрочной перспективе. А, значит, следует сделать тестирование самой первой и наиболее приоритетной задачей, прежде чем приступать к сколь угодно серьезному рефакторингу. Если вас отговаривают от тестирования – спорьте, отстаивайте свое мнение. Иначе вся команда потерпит неудачу. Если избежать встречи с унаследованным кодом невозможно, прежде чем приступать к рефакторингу, протестируйте его. Лучшей стратегии попросту не существует. Отказ от тестирования — залог будущего поражения.

Что еще почитать:

Самые распространённые проблемы тех, кто начинает учить Java

Создание простейшего веб-проекта в IntelliJ Idea Enterprise. Пошагово, с картинками

Вопрос-ответ: как в Java правильно конвертировать String в int?