Существует обратная сторона данного шаблона. Иногда, если перед вами стоит сложная проблема, требуется, наоборот, поднажать, поднапрячься и потратить дополнительное время и усилия, чтобы решить ее. Однако большинство программистов инфицировано духом саморазрушения: «Я угроблю свое здоровье, отрекусь от своей семьи и даже выпрыгну из окна, лишь бы этот код заработал». Поэтому я не буду давать здесь каких-либо советов. Если вы чувствуете, что у вас развивается болезненное пристрастие к кофе, наверное, вам не стоит делать слишком частых перерывов. В крайнем случае, просто пройдитесь.
Что делать, если вы зашли в тупик? Выкиньте код и начните работу сначала.
Вы заблудились. Вы решили передохнуть. Вымыли руки. Еще раз попытались вспомнить дальнейший путь. И все равно вы заблудились. Код, который выглядел так неплохо всего час назад, теперь выглядит запутанно и непонятно, одним словом, отвратительно. Вы не можете представить себе, как заставить работать следующий тест, а впереди у вас еще 20 тестов, которые необходимо реализовать.
Подобное случалось со мной несколько раз, пока я писал эту книгу. Код получался слишком кривым. «Но я должен закончить книгу. Мои дети хотят есть, а сборщики налогов стучаться в мою дверь.» У меня возникает желание выпрямить код настолько, чтобы можно было продолжать двигаться вперед. Однако на самом деле в большинстве случаев продуктивнее отдохнуть немного и начать все заново. Однажды я был вынужден выкинуть 25 страниц рукописи потому, что она была основана на очевидно глупом программистском решении.
Хорошую историю на эту тему рассказал мне Тим Макиннон (Tim Mackinnon). Однажды он проводил собеседование с потенциальным новым сотрудником. Чтобы оценить уровень его мастерства, он предложил ему программировать в паре в течение часа. К концу этого часа они реализовали несколько новых тестов и провели несколько сеансов рефакторинга. Однако это был конец рабочего дня, они оба чувствовали себя усталыми, поэтому решили полностью убрать из системы результаты своей работы.
Если вы программируете в паре, смена партнера – это хороший повод отказаться от плохого кода и начать решение задачи с начала. Вы пытаетесь объяснить смысл запутанного кода, над которым работали до этого, и вдруг ваш партнер, совершенно не связанный с ошибками, которые вы допустили, берет у вас клавиатуру и говорит: «Я ужасно извиняюсь за мою тупость, но что, если мы попробуем начать по-другому?»
В какой физической обстановке следует использовать TDD? Используйте удобное, комфортное кресло. На всей остальной мебели можно сэкономить.
Вы не сможете хорошо программировать, если ваша спина будет болеть. К сожалению, организации, которые выкладывают по $100 000 в месяц за работу команды программистов, как правило, отказываются тратить $10 000 на покупку хороших кресел.
Я предлагаю использовать дешевые, самые примитивные и ужасные на вид столы для установки компьютеров, но купить самые лучшие кресла, которые я только смогу найти. Дешевых столов можно купить столько, сколько нужно, значит, я получаю в свое распоряжение достаточное количество рабочего места и могу легко его увеличить. При этом я чувствую себя комфортно за компьютером, моя спина не устает.
Если вы программируете в паре, позаботьтесь о том, чтобы вам было удобно. Расчистите пространство на столе, чтобы вам было удобно передавать клавиатуру из рук в руки. Когда я работаю наставником, я люблю выполнять один простой прием: незаметно подходить со спины к программирующей паре и ненавязчиво поправлять клавиатуру так, чтобы она располагалась удобно по отношению к человеку, который с ней работает.
Манфред Лэндж (Manfred Lange) считает, что аккуратное распределение ресурсов необходимо выполнить также в отношении компьютерного аппаратного обеспечения. Рекомендуется использовать дешевые/медленные/старые компьютеры для индивидуальной электронной почты и работы с Интернетом, но зато приобрести самые современные и самые быстрые компьютеры для разработки.
27. Шаблоны тестирования
В данной главе более подробно описываются методики разработки тестов.
Как заставить работать тест, который оказался слишком большим? Напишите тест меньшего размера, который представляет собой неработающую часть большого теста. Добейтесь успешного выполнения маленького теста. Заново напишите большой тест.
Ритм красный – зеленый – рефакторинг чрезвычайно важен для достижения успеха. Не бойтесь потратить дополнительные усилия, чтобы поддерживать этот ритм, – дополнительные усилия с лихвой окупят себя. Я достаточно часто попадаю в подобную ситуацию: сначала записываю тест, а потом оказывается, что для его реализации требуется выполнить не одно, а несколько изменений. Я неожиданно оказываюсь на большом расстоянии от зеленой полосы. Даже десять минут с красной полосой заставляют меня нервничать.
Когда тест оказывается слишком большим, я, прежде всего, пытаюсь усвоить урок. Почему тест оказался слишком большим? Что надо было сделать иначе, чтобы тест получился меньше по размеру?
Покончив с размышлениями, я удаляю изначальный тест и начинаю заново. «Похоже, заставить все эти три вещи работать одновременно – это слишком сложная задача. Однако если я вначале добьюсь успешной работы A, B и C, мне не составит труда заставить работать всю эту штуку целиком.» Иногда я действительно удаляю тест, однако в некоторых случаях я просто изменяю его имя так, чтобы оно начиналось на x, – в этом случае тестовый метод не будет выполнен. (Скажу вам по секрету, что иногда я вообще не трогаю изначальный тест. Да, да! Только т-с-с-с! Никому об этом не рассказывайте! Слава богу, в большинстве подобных случаев мне удается быстро заставить работать дочерний тест. Однако получается, что я в течение пары минут живу вместе с двумя сломанными тестами. Возможно, когда я так поступаю, я совершаю ошибку. Этот пережиток сохранился у меня с тех времен, когда я выполнял тестирование после завершения разработки или вообще не тестировал свой код.)
Попробуйте оба варианта. Прислушайтесь к своим ощущениям. Если у вас есть два сломанных теста, вы, как правило, начинаете программировать иначе. Делайте выводы.
Как выполнять тестирование объекта, который базируется на сложном и тяжеловесном ресурсе? Создайте поддельную версию ресурса, которая будет возвращать константы.
Использование поддельных объектов – это тема для отдельной книги. Существует огромное количество материала, посвященного поддельным объектам[16]. Здесь я попытаюсь очень коротко познакомить читателей с этой концепцией.
Классическим примером является база данных. Чтобы запустить базу данных, требуется значительное время, поддержка чистоты базы данных требует дополнительных затрат, кроме того, если база данных располагается на удаленном сервере, ваши тесты будут связаны с конкретным физическим местоположением в сети. Наконец, база данных является емким источником ошибок разработки.
Чтобы уменьшить количество проблем, рекомендуется в процессе тестирования отказаться от работы непосредственно с базой данных. Большинство тестов пишется в отношении объекта, который функционирует подобно базе данных, однако располагается в оперативной памяти.
public void testOrderLookup() {
Database db = new MockDatabase();
db.expectQuery("select order_no from Order where cust_no is 123");
db.returnResult(new String[] {"Order 2","Order 3"});
.
}
Если объект MockDatabase не принимает ожидаемого запроса, он генерирует исключение. Если запрос корректен, объект возвращает нечто, напоминающее результирующий набор данных, состоящий из нескольких постоянных строк.
Помимо высокой производительности и надежности поддельные объекты обладают еще одним преимуществом: читабельностью. Если вы работаете с реальной базой данных, заполненной реальными данными, в результате обработки запроса вы можете получить ответ, состоящий из 14 строк. Возможно, вам будет нелегко понять, откуда взялось число 14 и в чем, собственно, состоит смысл теста.
Если вы хотите воспользоваться поддельными объектами, не следует хранить тяжеловесные ресурсы в глобальных переменных (даже если они замаскированы с использованием шаблона «Одиночка» (Singleton)). Если вы так поступите, вам придется вначале настроить глобальный поддельный объект, затем выполнить тест, а затем позаботиться о том, чтобы вернуть поддельный объект в исходное состояние.
В свое время я очень строго следил за выполнением этого правила. Мы вместе с Массимо Арнольди (Massimo Arnoldi) разрабатывали код, который взаимодействовал с набором курсов обмена валют, хранящимся в глобальной переменной. Для разных тестов требовалось использовать разные наборы данных, и в некоторых случаях курсы обмена валют должны были быть разными для разных тестов. Вначале мы пытались использовать для тестирования глобальную переменную, однако в конце концов нас это утомило, и однажды утром (смелые решения, как правило, приходят ко мне по утрам) мы решили передавать объект Exchange (в котором хранились курсы обмена) в качестве параметра везде, где это было необходимо. Мы думали, что нам придется модифицировать сотни методов. Однако дело кончилось тем, что мы добавили дополнительный параметр в десять или пятнадцать методов и по ходу дела подчистили другие аспекты дизайна.
Шаблон поддельных объектов заставляет тщательно следить за видимостью объектов, снижая взаимозависимости между ними. Поддельные объекты добавляют в проект некоторый риск, – что, если поддельный объект ведет себя не так, как реальный объект? Чтобы снизить этот риск, вы можете разработать специальный набор тестов для поддельных объектов, которые должны быть выполнены в отношении реального объекта, чтобы убедиться в том, что имитация достаточно близка к оригиналу.