Как стать автором
Обновить

Еще немного о TDD и модульных тестах

Время на прочтение5 мин
Количество просмотров3.7K
На хабре полно адептов TDD, и уже не раз всплывали статьи про разработку методом тестирования. Хочу внести и свои пять копеек статьей про этот замечательный инструмент.

При взгляде новичка на тесты сразу возникает вопрос: а зачем вообще писать лишний код? Вроде как преимущества TDD никто не отрицает, но находятся какие нибудь причины: «да, я слышал что TDD полезен в больших проектах, но у нас проект маленький», «в нашем проекте слишком много изменений, поэтому тесты для нас слишком большая обуза» и так далее. Попробую рассказать как модульные тесты помогают мне в работе и поделиться опытом использования.


1. Упрощение поддержки кода


TDD идет рука об руку с рефакторингом, и это самая очевидная причина использования тестов, поскольку он встроен в идеологию red-green-refactoring. Программный код, как и любая система, имеет свой жизненный цикл, его надо поддерживать, изменять, улучшать, иначе рано или поздно он разрушится и мы дойдем до состояния «все очень плохо, надо все переписать». Как правило это психологическая реакция программистов, которым настолько тяжело разбираться в коде, потерявшем ясную цель и переполненным костылями, что последствия внесенных изменений могут появиться непонятно когда и каким образом. Приходится или писать тесты (что долго, нудно и уже не соответствует принципу «сначала тест, потом код»), скрепя сердце рефакторить без тестов (рискуя получить кучу ошибок), или даже начать все с нуля. Признаюсь, раньше и сам любил выбросить кусок плохого кода, теперь же считаю что лучше пересилить себя и начать рефакторить без тестов, чем выбрасывать наработки — ошибок будет меньше. Если есть возможность — напишите тесты. Инструментарий рефакторинга мощен, рано или поздно кусок кода начнет принимать удобоваримый вид. Не поддавайтесь темной стороне…
Советовать всегда писать тесты не буду, ибо часто можно ошибиться и в тесте, и тест пропустить. Универсального метода я не вижу.

2. Четкое разделение структуры на модули


Как правило человек, начавший использовать TDD, уже понимает, что для написания компактных тестов модули должны иметь хорошо описанные интерфейсы. Меняем код, входные и выходные данные неизменны, все хорошо.
Проблемы здесь начинаются, когда идет изменение интерфейсов. Именно это приводит к переписыванию тестов, которые могут составлять довольно большой процент от всего кода. Соломки везде не подстелишь, иногда приходится менять и тесты. Это в свою очередь ведет и к тому, что код тестов тоже надо рефакторить, и тут уже кое-кто начинает писать тесты на тесты и понеслось…
Проблема сложная, однозначного решения я не вижу. Нашему брату программисту в решении этой проблемы помогает опыт и стратегическое видение развития системы.

3. Борьба со сложностью


Хорошие люди вроде Макконнелла, Брукса и других пишут о главной, по их мнению, проблеме ПО — сложности. Сложность растет непропорционально количеству кода. Возможно, такой проблемы бы не было, если программист смог загружать к себе в голову все требования. Но увы, наши ресурсы органичены. Человечество придумало отличный способ борьбы с этим — введение иерархий: методы инкапсулируются в классы, классы в пакеты, пакеты в библиотеки. Из них как из кирпичиков строятся более сложные приложения. Но что делать если описание алгоритма просто разрывает мозг, а он вполне себе цельный? Возможно я глупый, но некоторые алгоритмы иногда вводят меня в ступор: пока работаешь над одной частью, совершенно забываешь другую; все требования к коду просто не помещаются в мозг в один момент времени. В таких случаях тесты для меня единственное спасение: даже несмотря на увеличение исходного кода (а часто в много раз, так как в небольшом с виду алгоритме иногда имеется большое число сценариев поведения), экономится время за счет меньшего числа регрессионных ошибок в будущем. Особенно если фидбэк по ошибкам идет долго. Немаловажную роль тут играет и психологическая сторона: используя маленькие итерации по мантре TDD подгоняешь себя разобраться с этим ненавистным участком кода.
Однако тесты — не панацея, и многие предостерегают от желания 100% покрытия тестами (что в таком случае делать с кодом, генерируемым средой разработки? и другие неприятные последствия). Если код прост, написание на него тестов увеличивает сложность решения не давая ничего взамен, и соответственно не прибавляет ему стоимости. Как оценить сложность? Не знаю, мне иногда внутренний голос подсказывает, что без тестов тут не обойтись.
Цель разработчика должна быть не в том, чтобы фанатично покрыть код на 100% тестами, или ровно на 5%, а в том чтобы создать сложное решение, балансируя между временем разработки и качеством, наличием тестов и их отсутствием. В моей копилке проекты покрыты тестами примерно от 0 до 20%, причем я не вижу однозначной зависимости от параметров проекта: числа людей, длительности разработки или перетасовывания людей в команде. Сразу оговорюсь что число людей в одной команде не превышает трех (работаем маленькими командами), а длительность одного проекта не превышает 2 000 человеко-часов разработки.

4. Тесты как спецификация


У нас есть техническое задание, в котором можно увидеть описание алгоритмов и сценариев работы. Что мы можем получить от тестов? ТЗ описывает желаемый результат со стороны конечного пользователя, а свое видение решения в момент создания приложения программист описывает с помощью комментариев, правил оформления кода (говорящие имена методов и т. д.) и иногда тестов. Тесты позволяют отобразить желаемое программистом поведение, что пригодится при поддержке, когда сложно понять, что же там зарыто: хитрая задумка, временная мера или просто неаккуратность. Естественно, это тоже не панацея, ничто не защищает нас от такой пакости как «пропущенный тест».
Сейчас в одном проекте я пишу тесты и там моя первоначальная цель — именно спецификация. У меня решение из ~100 модулей-плагинов, каждый из которых в отдельности прост и элегантен, однако все вместе они нанизываются на системные вызовы основной системы как мясо на шашлык и требуют к себе частого внимания: у клиентов часто изменяются бизнес-процессы или они придумывают более эффективные способы использования. Зоопарк модулей приходится держать под строгим контролем, чтобы при необходимости быстро оценить риски изменений и время разработки.

5. Психология


Про психологическую сторону я уже писал выше, еще раз хочу ее выделить. TDD еще и привносит принципы итерирования на уровень работы с исходным кодом: планирование нового изменения — внесение изменения — доработка изменения aka red-green-refactoring. Отличный способ оторваться от изучения алгоритма/схемы/диаграммы и пересилив себя сделать небольшой шажок вперед. Потом опять вернуться обратно и все повторить.
Обычный waterfall здесь не так проигрывает: если алгоритм помещается в голову сразу весь, можно и не итерировать. Если не идет — пробуйте другие способы.
Плюс тесты позволяют почувствовать уверенность в коде, и помогают избавиться от принципа «работает — не трожь». Любые отрицательные изменения сразу покажут себя.

Заключение


Все перечисленные цели довольно близки друг к другу, используя TDD ради одной из них вы получаете все эти преимущества, как впрочем и недостатки. Лишний инструмент никому не помешает, решение о его использовании ложится на плечи разработчика (или всей команды). Пишите хороший код, и да пребудет с вами Сила!:)

На хабре уже есть:
Теги:
Хабы:
+19
Комментарии29

Публикации