Специалисты из нескольких ВУЗов Европы – Давиде Фуччи, Джузеппе Сканиелло, Симоне Романе, Мартин Шеппэрд, Бойсе Сигвени, Фернандо Уйагуари, Бурак Туран, Наталья Юристо и Марку Ойиво – провели очередное исследование на тему эффективности тестирования ПО. Они рассмотрели методологии Test Driven Development (TDD) и Test Last Development (TLD).
Исследователи сравнивали их по двум показателям – суммарная скорость разработки продукта и качество исходного кода. Первая методология (разработка через тестирование – TDD) вновь не оправдала возложенных надежд: популярная ранее схема тестирования после разработки (TLD) оказалась не менее эффективной. Так что по указанным выше показателям существенных отличий они не обнаружили.
В таком случае чем же объясняется вспышка интереса к TDD, когда она только появилась? Эта методология возникла в 2000-х, так что теперь элемент новизны можно смело сбросить со счетов. Тем не менее, предметом споров она остается до сих пор.
Автор TDD Кент Бек выделяет пять основных этапов при использовании методологии на практике:
• Написать новый тест-кейс;
• Убедиться, что запуск нового теста приведет к сбою;
• Написать новый или модифицировать код так, чтобы тест прошел успешно;
• Перезапустить все остальные тесты и подтвердить успешное их прохождение;
• Сделать рефакторинг кода, устранив избыточность.
TDD и TLD имеют свои преимущества и недостатки. Причем, преимущество одной методологии зачастую является недостатком для другой.
• Скорость разработки тестов
В случае с TDD инженерам приходится тратить на 16% больше времени, чем с TLD. Это объясняется дополнительным затратами на переключение разработчиков между написанием тестов и основного кода.
• Порог вхождения
У TDD выше порог вхождения, так как это не только методология тестирования, это иной подход к разработке ПО. Поэтому разработчику необходимо время, чтобы понять его и начать использовать на практике.
• Производительность и сопровождение
Благодаря TDD снижаются затраты на сопровождение программного продукта. Обычно при использовании этой методологии количество тест-кейсов примерно на 50% больше, чем в случае с TLD. Это дает большее покрытие и подразумевает повышенную надежность продукта. Поэтому и сопровождать такое ПО легче. За счет хорошо продуманной архитектуры производительность системы, разработанной с использованием, TDD обычно выше.
• Объем кода
Применение TLD позволяет существенно сократить объем кода по сравнению с TDD. Кроме того, код с TLD зачастую имеет более простую структуру.
• Внесение изменений
С TDD разработчики обычно могут быть уверены, что изменения не вызовут нежелательных эффектов. Все необходимые тесты запускаются после каждого внесенного изменения. Благодаря разработке через тестирование отладка ПО в целом становится более прозрачной и осознанной.
В любом случае каждый проект индивидуален, но общие закономерности существуют — так же, как и существуют различные компромиссы.
Компромиссы
Иван Хватов:
И то, и другое – крайности. Всё зависит от ситуации. В целом, тесты – это очень больная и холиварная тема. Вопрос ещё не закрыт о том, какие тесты вообще писать надо или не надо: юнит, функциональные, или интеграционные (или все).
Приведем пример возможного диалога между менеджером и архитектором.
А: — Хотим ли мы 100% покрытий?
М: — Да, конечно.
А: — Отлично, только это затянет разработку вдвое и усложнит сопровождаемость тестового кода на порядок.
Широко распространено мнение о том, что дизайн среднего качества обеспечит покрытие ключевых кусков кода на 80-85%, а каждые последующие 5% будут отнимать все больше и больше ресурсов (труда и времени).
Более того, автор TDD Кент Бек как-то высказывал мысль, что ненужные тесты можно удалять. Такие тесты он называет delta coverage – дополнительное покрытие, обеспечиваемое конкретным набором тестов. Если это дополнительное покрытие равно 0, то тест можно смело удалять.
Кент Бек — разработчик программного обеспечения, создатель таких методологий разработки ПО как экстремальное программирование (XP) и разработка через тестирование (TDD). Бек был одним из 17 специалистов, подписавшихAgile Manifesto в 2001 году.
Лишние тесты приводят к дополнительным затратам на сопровождение. Именно поэтому, тесты, полученные на ранних этапах обычно являются слишком наивными и могут быть удалены. Особенно это касается методологии TDD.
Мартин Фаулер привел два простых эмпирических правила, которые определяют нижнюю и верхнюю границы покрытия: если вы не уверены в своих изменениях, и многие из них приводят к сбоям, но не ломают тесты, значит тестов слишком мало. Если же при каждом изменении ломается слишком много тестов, то, возможно, тестов слишком много.
Мартин Фаулер — автор ряда книг и статей по архитектуре ПО, объектно-ориентированному анализу и разработке, языку UML, рефакторингу, экстремальному программированию, предметно-ориентированным языкам программирования.
Когда стоит использовать TDD?
На графике изображена скорость развития двух проектов. Синие принесли в жертву дизайн и тесты. Их цель – как можно скорее реализовать максимальный объем функциональности. Красные, напротив, уделяют большое внимание дизайну системы и её тестированию.
Очевидно, что синие оставляют в коде много «технических долгов», чем обрекают себя на трудности при расширении системы в будущем и проблемы с её поддержкой.
Но за счёт чего красные умудряются держать систему гибкой и двигаться со стабильной скоростью? Одна из причин — это использование TDD, пишет Александр Бындю, эксперт по Agile и Lean.
Он приводит ряд преимуществ TDD в проекте красных:
1. Самое простое – в таком проекте больше тестов, по сравнению с обычным подходом. С одной стороны, само по себе количество тестов не гарантирует более высокого качества проекта и отсутствия ошибок. С другой, всё зависит от того, насколько хорошо вы умеете писать эти тесты. Много хороших тестов сэкономят вам много времени.
2. Код становится более качественным. Это связано с тем, что модульное тестирование подразумевает слабую связанность различных модулей системы, иначе будет очень сложно написать эти модульные тесты. Поэтому приходится применять, например, принципы проектирования классов S.O.L.I.D:
Принцип единственности ответственности (The Single Responsibility Principle)
Принцип открытости/закрытости (The Open Closed Principle)
Принцип замещения Лисков (The Liskov Substitution Principle)
Принцип разделения интерфейса (The Interface Segregation Principle)
Принцип инверсии зависимости (The Dependency Inversion Principle)
В конечном счёте, это даст всей системе большую мобильность и гибкость.
3. Мы можем перестраивать наш проект сколько угодно, адаптировать его к новым требованиям и не бояться, что после очередного рефакторинга мы потеряем какую-то уже работающую функциональность. Почему мы можем себе это позволить? Потому что после рефакторинга запустим все тесты, и они нам покажут (зеленой полоской), что все бизнес-функции до сих пор работают.
Поэтому, по мнению Бындю, TDD вынуждает писать более качественный код, который удобнее тестировать. А это значит, что мы оставляем в коде меньше технических долгов.
Когда же TDD начинает обгонять? Тогда, когда синяя и красная линия пересекаются. В это время команда синих вовсю начинает платить за долги. Отсюда вывод о границах применимости TDD:
Вы можете писать код без TDD, если уверены, что до пунктирной линии дело не дойдет. Например, вы пишете небольшой проект для автоматизации внутренней работы, или создаете сайт-визитку.
Надо понимать, что TDD – не гарантия качества вашего кода, но с ним проще держать систему в тонусе, заключает он.
Не одно «но»
Дэвид Ханссон предупреждает, что увлеченность тестированием и попыткой протестировать все в полной изоляции (когда мы тестируем лишь текущий класс, без его зависимостей или вспомогательных низкоуровневых классов) приводит к нагромождению ненужных слоев абстракции и переусложненному дизайну.
Давид Хейнемейер Ханссон — датский программист, автор веб-фреймворка Ruby on Rails, основатель Instiki wiki и автогонщик, победитель в классе LMGTE Am (2014) и серебряный призёр LMP2 (2013) чемпионата мира по гонкам на выносливость, победитель 24 часов Ле-Мана 2014 года в классе LMGTE Am.
Однако у Мартина Фаулера есть важная оговорка на этот счет:
Когда система начинает разрастаться, то проблема не в количестве уровней абстракции, а в количестве уровней изоляции. То есть, сам факт того, что класс А вызывает метод класса Б, который обращается к классу В не представляет проблему до тех пор, пока между каждым из этих классов не появляется уровень изоляции в виде интерфейса.
С другой стороны на эту тему смотрят некоторые пользователи «Хабра».
Нужно понимать, что применение таких методологий как TDD предполагает, что автотесты пишут разработчики, а не тестировщики. Это методологии программирования и кодирования, а не разработки готового продукта, в которую вовлечено множество людей от ПМов и техписателей до админов и техподдержки. Разработчики же зачастую относятся к внедрению TDD-like как взваливанию на них обязанностей тестировщиков.
TDD действительно не то, что должны делать тестировщики. Их задача писать функциональные тесты и, если хватает квалификации, интеграционные. TDD не TDD, но разработчики испокон веков занимались тестированием, запуская свой код и проверяя как он работает, юнит-тестирование это скорее формализация этого процесса.
Если тест написан до и предоставлен программисту в качестве исходных данных, он становится «техническим требованием» (requirement). Разумеется, тот, кто пишет тест, должен заранее написать и запрограммировать все требуемые моки. Ха-ха, не хотел бы я быть тем программистом, в обязанности которого входит программировать тесты для других.
Ну, а может ли сам программист написать тесты к своему коду заранее? По моему опыту, нет и нет. Программисты (и я и подавляющее большинство, с кем я это обсуждал) просто думают в обратном направлении. Да, поначалу люди пытались честно следовать методологии. Но спустя какое-то время, начинали сначала писать код, а потом на него дописывать тесты. А еще спустя какое-то время, тесты потеряли свою разносторонность и дотошность. А потом и вовсе писались «штоб было», раз уж просят.
А оно вообще надо?
Адаптация TDD
Свое мнение также высказывал один из апологетов разработки через тестирование Александр Люлин:
Я не использую TDD в его классическом понимании. Вообще, вряд ли кто-то из профессионалов рассматривает энциклопедические статьи в качестве руководства к действию. Мы свой подход «выстрадали» в рамках реализации успешного проекта, поэтому за нами реальный опыт, а не «тупое использование чужих идей».
Скорее, мы используем синтез из TDD и собственных представлений о том, как нужно разрабатывать ПО. Даже если эти «внешние идеи» исходят от очень умных людей, их следует критически осмыслить и адаптировать к реальной компании, существующей команды и стратегии развития и обеспечения качества.
Олег Балбеков, СЕО Vexor:
На наш взгляд жестко разделять данные подходы довольно сложно и вообще не нужно. Так повелось, что в нашей команде мы обычно совмещаем использование обоих подходов в одном проекте, и даже иногда при разработке одного блока кода.
Ну, например, когда мы пишем часть проекта для работы с сервисами через API, мы пишем сначала тесты и уже потом код (TDD). Однако после реализации часто бывает так, что написанных заблаговременно тестов недостаточно и приходится их дописывать, добиваясь 100% покрытия (TLD).
Иногда даже на очень простые контроллеры (обычный ресурс с базовыми CRUD), сначала пишутся простые тесты, а потом уже код (TDD). Это нам дает уверенность в том, что ничего не забыто в первоначальной реализации. В дальнейшем, при развитии проекта, в большинстве случаев тесты пишутся уже после написания кода (TLD).
Часто есть задачи реализации алгоритма на известные входные/выходные данные, тут тоже в основном используется TDD подход. Если есть готовые входные и выходные данные, то по ним очень легко написать простой тест, а уже потом реализовать этот алгоритм, будучи уверенным в результате.
Чаще всего TLD подход применяется во время рефакторинга. Сначала мы покрываем тестами существующую реализацию и уже потом переписываем ее.
Если говорить про соотношение использования TDD и TLD в проектах, то выигрывает обычно TLD. Разработчиков, которые просто пишут тесты всегда больше тех, кто в состоянии «думать тестами» и писать тесты заблаговременно.
Автор: semen_grinshtein