Почему я был вынужден переписать заново то, что я уже писал до этого не меньше 20 раз? Буду ли я и дальше сталкиваться с подобными сюрпризами? Существует ли способ, который позволит мне найти правильное решение, по крайней мере в течение первых трех попыток? А может быть, этот способ позволит мне найти правильное решение с первой попытки?
Использование JUnitЯ поручил инфраструктуре JUnit вести журнал в процессе разработки мультивалютного примера. Выяснилось, что за все время я нажал клавишу Enter ровно 125 раз. Оценку интервала между запусками тестов нельзя считать достоверной, так как в ходе работы я не только программировал, но и писал текст книги. Однако когда я занимался только программированием, я запускал тесты приблизительно раз в минуту.
На рис. 17.1 представлена гистограмма интервалов между запусками тестов. Большое количество длительных интервалов, скорее всего, обусловлено тем, что я тратил значительное время на написание текста книги.
Рис. 17.1. Гистограмма интервалов времени между запусками тестов
Метрики кодаВ табл. 17.1 приводятся некоторые статистические данные, характеризующие код.
Таблица 17.1. Метрики кода
Вот некоторые примечания к данной таблице:
1. Мы не реализовали весь программный интерфейс (API) целиком, поэтому не можем достоверно оценить полное количество функций, или количество функций на один класс, или количество строк кода на один класс. Однако соотношения этих параметров можно считать поучительными. Количество функций и количество строк в тестах приблизительно такое же, как и в функциональном коде.
2. Количество строк кода в тестах можно сократить, если извлечь из кода операции подготовки тестовых данных. Однако общее соотношение между строками функционального кода и строками тестирующего кода при этом сохранится.
3. Цикломатическая сложность (cyclomatic complexity) – это величина, характеризующая сложность обычного потока управления в программе. Цикломатическая сложность тестов равна 1, так как в тестирующем коде нет ни ветвлений, ни циклов. Цикломатическая сложность функционального кода близка к единице, так как вместо явных ветвлений для передачи управления чаще используется полиморфизм.
4. Оценка количества строк в функции дана с учетом заголовка функции и закрывающей скобки.
5. Количество строк на функцию для тестирующего кода в нашем случае больше чем могло бы быть, так как мы не выделили общий код в отдельные функции. Об этом рассказывается в главе 29, которая посвящена методам работы с xUnit.
ПроцессЦикл TDD выглядит следующим образом:
• написать тест;
• запустить все тесты и убедиться, что добавленный тест терпит неудачу;
• внести в код изменения;
• запустить тесты и убедиться, что все они выполнились успешно;
• выполнить рефакторинг, чтобы устранить дублирование.
Если исходить из того, что разработка теста – это один шаг, какое количество изменений требуется сделать, чтобы выполнить компиляцию, запуск и рефакторинг? (Под изменением я подразумеваю изменение определения метода или класса.) На рис. 17.2 показана гистограмма количества изменений для каждого из тестов «денежного» примера, над которым мы работали в первой части книги.
Рис. 17.2. Гистограмма количества изменений, приходящихся на каждый период рефакторинга
Я полагаю, что если бы мы собирали статистику для достаточно крупного проекта, мы обнаружили бы, что количество изменений, необходимых для компиляции и запуска кода, очень невелико (это количество можно уменьшить, если среда разработки будет понимать, что пытаются ей сказать тесты, и, например, автоматически добавлять в функциональный код необходимые заглушки). Однако количество изменений, вносимых в код во время рефакторинга, должно соответствовать (вот главный тезис) кривой распределения с эксцессом больше нормального, то есть с большим числом изменений, чем предсказывается стандартной кривой нормального распределения. Подобный профиль характерен для многих других естественных процессов, например для изменения стоимости акций на рынке ценных бумаг[9].
Качество тестовТесты являются неотъемлемой частью методики TDD. Они могут запускаться в любое время работы над программой, а также после того, как программа будет завершена. Однако не стоит путать их с другими важными типами тестирования:
• тестированием производительности;
• нагрузочным тестированием;
• тестированием удобства использования.
Тем не менее, если плотность вероятности дефектов в коде, разработанном с использованием TDD, невелика, роль профессионального тестирования меняется. Если обычно профессиональное тестирование используется для постоянного надзора за работой программистов, то при использовании TDD профессиональное тестирование больше напоминает вспомогательный инструмент, облегчающий коммуникацию между теми, кто знает, как должна работать система, и теми, кто создает систему.
Как можно оценить качество разработанных нами тестов? Вот два широко распространенных метода:
Охват кода (statement coverage). Для оценки качества тестов этой характеристики недостаточно, однако ее можно использовать как отправную точку. Если программист ревностно следует всем требованиям TDD, тесты должны охватывать 100 % кода. Для оценки этой характеристики можно использовать специальные программные средства. Например, программа JProbe (www.sitaka.com/software/jprobe) сообщает нам, что в нашем примере не охваченной тестами осталась всего одна строка в одном методе – Money.toString(). Напомню, что эта строка была добавлена в отладочных целях, фактически она не является функциональным кодом.
Намеренное добавление дефекта (defect insertion). Это еще один способ проверки качества тестов. Идея проста: изменить значение строки кода и убедиться, что тест перестал работать. Делать это можно вручную или при помощи специального инструмента, такого как Jester (jester.sourceforge.net). Этот инструмент сообщает нам, что в нашей программе существует всего одна строка, которую можно изменить, не нарушив работы тестов. Вот эта строка: Pair.hashCode(). Здесь мы просто подделали реализацию – вместо хеш-кода метод возвращает постоянное значение: 0. Если одно постоянное значение заменить другим, смысл программы не изменится (одна подделка ничем не лучше другой), поэтому подобную модификацию кода нельзя считать дефектом.
Флип, один из рецензентов моей книги, сообщил мне некоторые дополнительные соображения относительно охвата тестами. Абсолютный показатель охвата вычисляется следующим образом: количество тестов, предназначенных для тестирования различных аспектов программы, необходимо разделить на количество аспектов, которые нуждаются в тестировании (сложность логики программы). Существует два способа улучшить показатель охвата тестами. Во-первых, можно написать больше тестов. Отсюда разница в количестве тестов, которые пишутся разработчиком, использующим TDD, и профессиональным тестером. (В главе 32 приводится пример задачи, для решения