TDD Save Lifes
Как тестирование спасаем мои нервы
Иногда мне кажется, что я все больше говорю о тестировании, чем использую его. Конечно тут имеется в виду автоматическое тесторование и разработка через тестирование в частности. Поговорим о банке.
В кишках банка есть жирный пласт предметной логики, а точнее расчетов. Мне приходится работать с ООП+ORM+БД == Django. В связи с этим, а так же в силу сложности в широком смысле этого слова, у меня появилась некоторая система классов, котрые связаны друг с другом:
- Событие
- Аккаунт - суть пользователь
- Транзакция - действие пользователя в рамках события(принятие участие, “выход” и перерасчет)
- Участие - нить Ариадны: связывает событие и аккаунт с заданным коэффициентом.
В самом начале разработки, я сразу старался следовать TDD - сразу начал писать тесты. Логика связана с баблом, потому требует прозрачности и надежности. Тестов словом.
Сначала родились простые тесты что называется “в лоб”. В то время, я еще хранил операции предметной области в моделях.
Затем у меня возникли проблемы с этим: некоторые операции логично было держать в одних моделях, но в самой операции требовалось наличие других, которые в свою очередь в своем определении использовали первые модели. Все это привело к ужасным циклическим зависимостям и… я решил воткнуть немного ФП. Такое решение принесло сразу пользу.
И, конечно, когда я рефакторил целевой код, я рефакторил тесты. Это было нудно. Но когда я закончил, я спокойно отрефакторил клиентский код не особо парясь “А ничего не отвалится?” или “Все точно работает как раньше?”. Эти вопросы были риторическими в данном случае: тесты отвечают.
Затем время от времени появлясь такие штуки, как баги. Баги в целевом коде. Да, на каждый баг я лепил еще один тесты(или целый кейс). Тут вступала регрессионная сила тестов: я не боялся сломать то, что уже работало. Точнее, по привычке я боялся конечно, но когда менял код, я сразу видел что и где отваливается и сразу же понимал, что мое текущее решение - некорректно. Такими шагами я находил рабочее решение. Стыдно признать, но разработка в таком случае содержала этапы:
- анализ что нужно починить
- [Итерационно пока не заработает] А что если заменить > на < ?
- Сохранить, в продакшн.
Конечно я пренебрегаю порой рефакторингом. TDD - это же не просто “пиши тесты”, это красный -> зеленый -> рефакторинг!
Только что я отвлекся на рефакторинг. Рекурсивно так получается: пишу в блог про тестирование, говорю что надо рефакторить и что я этого не делаю - сразу иду и исправляюсь.
Отрефакторить потом код становится намного проще. Более того, рефакторинг может выявить непокрытие места. С регресами на готове, рефакторить не страшно: они как индикаторы(приборы) которые сообщают мне о той или иной фиче и ее работоспособности.
В общем, тесты - это предохранитель для нервов.
Дублирование в тестах
Тесты - это так же документация
Порой несколько(а то и все) тестов в кейсе похожи настолько, что руки чешутся выделить общую часть. С одной стороны - это конечно да. =) С другой - тест становится менее читаемым; из него становится сложно выяснить как же использовать тестируемый компонент. Палка о двух концах - дублировать или нет?
Я не дублирую. И порой страдаю от выделенных общих частей: появляются какие-то странные функции, которые не понятно что делают. Понятно конечно же мне, но я чувствую: покажи я этот код коллегам и они нахмурят лбы. Не на долго: потом начнутся метания от первых строк к телу конкретного теста и сопуствующее “Ага…ага…а-а-а-а…” будет фоновой музыкой.
Конечно дублирование пораждает такую проблему, как усложнение поддержки. Но если учесть, что рефакторить тесты приходится намного реже чем код, то этим можно пренебречь.
Еще вариант использовать нечно вроде препроцессора. Например Gtest-pump
- скрипт от разрабов Gtest, который они используют для генерации boilerplate-кода. Это как бы не большая надстройка в виде циклов/условий, с помощью который генерится исходник с кучей дублирования.