Делай как вGoogle
![]()
|
217 Большие тесты Наконец, у нас есть большие тесты, которые взаимодействуют не только с localhost , но и с другими машинами. Например, такой тест может проверять работу системы, действующей в удаленном кластере. Как и прежде, с увеличением гибкости теста растут риски. Необходимость взаимо- действовать с системой, охватывающей несколько машин, соединенных сетью, зна- чительно увеличивает вероятность медленного и недетерминированного выполнения теста по сравнению с ситуацией, когда тестирование выполняется на одной машине. Мы используем большие тесты в основном для сквозного и комплексного тестиро- вания, в ходе которого чаще проверяются различные конфигурации, а не фрагменты кода, а также для тестирования устаревших компонентов, для которых невозможно использовать дублеров. (Подробнее о больших тестах в главе 14.) Команды в Google часто изолируют большие тесты от маленьких или средних тестов и выполняют их только во время сборки и перед выпуском, чтобы не оказывать влияния на рабочий процесс разработки. Общие свойства тестов всех размеров Все тесты должны быть герметичными: тест должен иметь всю необходимую инфор- мацию для создания, настройки и удаления его внутренней среды. Тесты должны делать как можно меньше предположений о своей среде, в частности о порядке, в котором они выполняются. Например, они не должны полагаться на общую базу данных. Соблюсти это ограничение все сложнее с увеличением размеров тестов, но необходимо приложить усилия, чтобы обеспечить хороший уровень изоляции теста. Тест должен содержать только ту информацию, которая необходима для проверки некоего поведения. Ясность и простота тестов помогают рецензентам убедиться, что код делает именно то, что должен. Кроме того, чистый код помогает диагностировать обнаруженные ошибки. Мы любим говорить, что «тест должен быть очевиден для исследователя». Поскольку для самих тестов нет тестов, их правильность приходится проверять вручную. Мы настоятельно рекомендуем не использовать в тестах опера- торы управления потоком, такие как условные выражения и циклы ( https://oreil.ly/ fQSuk ), потому что они подвержены ошибкам и затрудняют определение причины неудачного выполнения теста. Помните, что тесты обычно пересматриваются, только когда что-то ломается. Когда вам предложат исправить сломанный тест, который вы никогда не видели раньше, вы будете благодарны тому, кто в свое время постарался сделать его максимально простым. Код читается гораздо чаще, чем пишется, поэтому пишите тесты так, чтобы их легко было читать! Выбор размера теста на практике. Точно определив размеры тестов, мы смогли со- здать инструменты, обеспечивающие соблюдение этих размеров, что, в свою очередь, помогло нам масштабировать наборы тестов с сохранением гарантий в отношении скорости, использования ресурсов и стабильности. Применение классификации 218 Глава 11. Основы тестирования тестов по размеру в Google зависит от языка. Например, все тесты Java мы запускаем с помощью специального диспетчера безопасности, который завершит неудачей любой тест, отмеченный как маленький, если тот попытается сделать что-то запре- щенное, например установить сетевое соединение. Широта охвата тестирования Мы в Google уделяем большое внимание размерам тестов, однако еще одним их важным свойством, которое необходимо учитывать, является широта охвата тести- рования — объем кода, проверяемого данным тестом. Тесты с узким охватом (их обычно называют юнит-тестами) предназначены для проверки логики в узкой об- ласти кодовой базы, такой как отдельный класс или метод. Тесты со средней широтой охвата (их также часто называют интеграционными тестами) предназначены для проверки взаимодействий между небольшим количеством компонентов, например между сервером и его базой данных. Тесты с широким охватом (их нередко называют функциональными, сквозными или общесистемными тестами) предназначены для проверки взаимодействий между несколькими отдельными частями системы или особенностей поведения кода, которые выражаются несколькими классами или методами. Важно отметить, что когда мы говорим об узком охвате юнит-тестов, мы имеем в виду проверяемый код, а не код, который выполняется. Часто классы имеют множество зависимостей от других классов, и эти зависимости, естественно, будут вызываться при тестировании целевого класса. Некоторые политики тестирования ( https://oreil. ly/Lj-t3 ) используют дублеров (имитации или фиктивные объекты), чтобы исключить возможность выполнения кода вне тестируемой системы (SUT, system under test), однако мы в Google предпочитаем по возможности сохранять для тестирования действительные зависимости (глава 13). Узкий охват, как правило, имеют маленькие тесты, а широкий — средние или боль- шие, но это не всегда так. Например, можно написать тест конечной точки сервера, охватывающий обширный код, который реализует синтаксический анализ запросов, их семантическую проверку и бизнес-логику, и этот тест будет считаться маленьким, потому что он использует дублеров для замены всех внепроцессных зависимостей, таких как база данных или файловая система. Точно так же можно написать тест среднего размера, охватывающий единственный метод. Например, при условии, что веб-фреймворк связывает вместе HTML и JavaScript, тестирование компонента пользовательского интерфейса, такого как элемент выбора даты, потребует запуска браузера, чтобы проверить единственный путь в коде. Мы в Google призываем инженеров писать не только маленькие тесты, но и тесты с как можно более узким охватом. В целом мы стремимся к тому, чтобы примерно 80 % наших тестов составляли юнит-тесты с узким охватом, подтверждающие пра- вильную работу основной бизнес-логики; 15 % — интеграционные тесты со средним охватом, проверяющие взаимодействия между двумя или более компонентами; Проектирование набора тестов 219 и 5 % — сквозные тесты, проверяющие систему целиком. Эти ориентиры показаны на рис. 11.3 в виде пирамиды. 5 % Сквозные тесты 15 % Интеграционные тесты 80 % Юнит-тесты Рис. 11.3. Версия пирамиды тестирования Майка Кона 1 ; доли тестов в процентах и их состав в разных командах будут немного отличаться Юнит-тесты формируют основу тестирования, потому что они быстрые, стабильные, сужают охват и снижают когнитивную нагрузку, необходимую для выявления всех возможных поведений класса или функции. Кроме того, они позволяют быстро и безболезненно диагностировать неисправности. Следует помнить о двух анти- паттернах — «перевернутая пирамида», или «рожок мороженого» (ice cream cone), и «песочные часы» (рис. 11.4). Тесты, выполняемые вручную Автоматизированные тесты пользовательского интерфейса Интеграционные тесты Интеграционные тесты Юнит- тесты Юнит-тесты Сквозные тесты Рис. 11.4. Антипаттерны в наборах тестов 1 Кон М. Scrum. Гибкая разработка ПО. М.: Вильямс, 2015. — Примеч. пер. 220 Глава 11. Основы тестирования Антипаттерн «перевернутая пирамида» соответствует ситуации, когда инженеры пишут много сквозных, но мало интеграционных или юнит-тестов. Такие наборы тестов часто оказываются медленными, ненадежными и трудными в отладке. Этот антипаттерн часто встречается в проектах, начинающихся с создания прототипов, которые быстро передаются в продакшен без приостановки для решения проблем тестирования. Антипаттерн «песочные часы» соответствует ситуации, когда в проекте есть много сквозных и юнит-тестов, но мало интеграционных тестов. Это не так плохо, как «перевернутая пирамида», но все же приводит к большому числу сбоев в сквозных тестах, которые быстрее и проще можно было бы обнаружить с помощью набора тестов со средним охватом. Антипаттерн «песочные часы» возникает, когда тесные связи между компонентами затрудняют создание автономных экземпляров неко- торых зависимостей. Рекомендуемое нами сочетание тестов определяется двумя основными целями: повышение продуктивности инженеров и укрепление доверия к продукту. Пред- почтительное отношение к юнит-тестам позволяет получить высокую степень уверенности в тестах с самого начала разработки. Большие тесты проверяют общую работоспособность продукта по мере его разработки, и их не следует рассматривать как основной метод выявления ошибок. Формируя свой набор тестов, вы можете выбрать другое их соотношение. Сделав упор на интеграционное тестирование, вы обнаружите, что тестовые наборы выполняются дольше, но выявляют больше проблем во взаимодействиях между компонентами. Уделив больше внимания юнит-тестам, вы заметите, что тестирование протекает очень быстро и выявляет больше типичных логических ошибок. Но юнит-тесты не в состоянии проверить правильность взаимодействий между компонентами, на- пример соблюдение контракта между двумя системами, разработанными разными командами ( https://oreil.ly/mALqH ). Хороший набор тестов содержит смесь тестов раз- ных размеров и уровней охвата, которые соответствуют локальным архитектурным и организационным реалиям. Правило Бейонсе Наши новые сотрудники, проходя первичное обучение, часто спрашивают, какие черты поведения или свойства действительно необходимо тестировать? Ответ прост: тестируйте все, что вы не хотели бы сломать. Другими словами, если вы хотите, чтобы система демонстрировала определенное поведение, единственный способ гарантировать это — написать автоматический тест, проверяющий его. К свойствам кода, которые важно протестировать, относятся: производительность, корректность поведения, доступность и безопасность, а также менее очевидные свойства, такие как обработка ошибок в системе. Используйте «правило Бейонсе» ( https://oreil.ly/X7_-z ). В контексте тестирования его можно сформулировать так: «Если вам что-то понравилось, добавьте для него тест». Проектирование набора тестов 221 Правило Бейонсе часто используется инфраструктурными командами, отвечающими за внесение изменений во всей кодовой базе. Если не связанные между собой изме- нения в инфраструктуре пройдут все ваши тесты, но вызовут нарушения в работе продукта вашей команды, устраните проблемы и добавьте дополнительные тесты. ТЕСТИРОВАНИЕ ОТКАЗОВ Отказ — одна из наиболее важных ситуаций, которую должна учитывать система. Отказы неизбежны, но нет ничего хуже, чем просто ждать реальной катастрофы, чтобы выяснить, насколько хорошо система способна справиться с ней. Вместо ожидания отказа напишите автоматизированные тесты, имитирующие типичные сбои: смоделируйте исключения или ошибки в юнит-тестах, сымитируйте отказы или задержки в интерфейсе RPC в интегра- ционных и сквозных тестах. Также можно смоделировать гораздо более масштабные на- рушения, влияющие на реальную производственную сети, с использованием таких методов, как Chaos Engineering ( https://oreil.ly/iOO4F ). Предсказуемый и контролируемый ответ на неблагоприятные условия является отличительной чертой надежной системы. Еще об охвате тестирования Охват кода — это мера, определяющая, какие строки кода и какими тестами выпол- няются. Если у вас есть 100 строк кода и ваши тесты выполняют 90 из них, то вы имеете 90%-ный охват тестирования 1 . Охват часто считается стандартной метрикой, описывающей качество набора тестов, что несколько ошибочно. С помощью несколь- ких тестов можно выполнить много строк кода, но так и не убедиться, что каждая строка делает что-нибудь полезное. (Мы рекомендуем измерять охват только для маленьких тестов, чтобы избежать переоценки этой метрики.) Еще более коварная проблема с оценкой охвата тестирования состоит в том, что, подобно другим метрикам, она быстро становится самоцелью. Обычно команды устанавливают планку ожидаемого охвата, например 80 %. Сначала это выглядит вполне разумно, ведь никто не откажется иметь такую степень охвата. Но на прак- тике получается, что инженеры относятся к этому значению не как к минимальному, а как к максимальному и создают набор тестов, охватывающий не более 80 % кода. В конце концов, зачем делать больше, чем требует стандарт? Лучший способ повысить качество набора тестов — подумать о тестируемом по- ведении. Есть ли у вас уверенность, что все будет работать точно так, как ожидают клиенты? Сможете ли вы заметить кардинальные изменения в ваших зависимостях? Ваши тесты стабильны и надежны? Подобные вопросы помогают глобальнее рас- суждать о наборе тестов. Разные продукты и команды имеют свои отличительные особенности: некоторые испытывают трудности при взаимодействии с оборудовани- ем, другие — с массивными наборами данных и т. д. Пытаясь ответить на вопрос: «До- статочно ли у нас тестов?» с помощью единственной метрики, вы рискуете упустить 1 Есть и другие аналогичные метрики охвата тестирования: число путей, ветвей и т. д. 222 Глава 11. Основы тестирования из виду множество аспектов. Метрика охвата может дать некоторое представление об объеме непроверенного кода, но эта оценка не способна заменить критический взгляд на качество тестирования системы. Тестирование в масштабе Google Большая часть рекомендаций по тестированию вполне применена к базам кода прак- тически любого размера. Тем не менее мы хотим потратить еще немного времени, чтобы рассказать, что мы узнали, проводя тестирование в огромном масштабе. Чтобы понять, как осуществляется тестирование в Google, необходимо понимать нашу среду разработки, наиболее важной чертой которой является хранение большей части кода в одном монолитном репозитории (monorepo ( https://oreil.ly/qSihi )). Почти весь код каждого продукта и службы, над которыми мы работаем, хранится в одном месте. В настоящее время в нашем репозитории хранится более двух миллиардов строк кода. Объем еженедельных изменений в кодовой базе Google достигает 25 миллионов строк. Примерно половина из них вносится десятками тысяч инженеров, работающих с монолитным репозиторием, а другая половина — автоматизированными системами в форме обновлений конфигураций или крупномасштабных изменений (глава 22). Многие из этих изменений инициируются за пределами конкретного проекта. Мы стараемся не ограничивать возможность повторного использования кода нашими инженерами. Открытость нашей кодовой базы способствует активному совместному владению: любой инженер чувствует ответственность за кодовую базу и может напрямую ис- правлять ошибки (конечно, с одобрения), вместо того чтобы жаловаться на них, а также вносить изменения в код, принадлежащий кому-то другому. Еще одна отличительная черта Google заключается в том, что практически ни одна команда не использует возможность создания ветвей в репозитории. Все изменения фиксируются в главной ветви и сразу доступны всем. Кроме того, сборка любого ПО осуществляется с использованием последних зафиксированных изменений, проверенных нашей инфраструктурой тестирования. Когда производится сборка продукта или службы, почти все необходимые зависимости собираются из исходного кода в главной ветви репозитория. Управление тестированием в таком масштабе происходит с помощью системы непрерывной интеграции, одним из ключевых компонентов которой является автоматизированная платформа тестирования (TAP, test automated platform). Подробнее о нашей философии TAP и непрерывной интеграции в главе 23. Инженерная среда в Google имеет очень сложную организацию независимо от того, что рассматривать — ее размер, монолитный репозиторий или количество предла- Тестирование в масштабе Google 223 гаемых продуктов. Каждую неделю в ней проверяются миллионы строк изменений, выполняются миллиарды тестов, создаются десятки тысяч двоичных файлов и сотни обновленных продуктов — попробуйте вообразить что-то еще более сложное! Недостатки больших наборов тестов C ростом кодовой базы вам неизбежно придется вносить изменения в существующий код. И плохо написанные автоматизированные тесты могут усложнить выполнение этой задачи. Хрупкие тесты, переопределяющие ожидаемые результаты или по- лагающиеся на обширные и сложные шаблоны, могут фактически препятствовать добавлению изменений. Такие тесты могут терпеть неудачу даже при внесении из- менений, никак не связанных с тестируемым кодом. Если вам приходилось обнаруживать десятки неработающих тестов после изменения пары строк в функции, значит, вы уже имели возможность почувствовать сопро- тивление хрупких тестов. Со временем это сопротивление может заставить команду избегать рефакторинга, необходимого для поддержания кодовой базы в здоровом состоянии. В следующих главах мы рассмотрим политики повышения надежности и качества тестов. К худшим нарушениям в работе тестов относится неправильное употребление фиктивных объектов. База кода в Google так сильно пострадала от злоупотребле- ния фреймворками для создания фиктивных объектов, что заставила некоторых инженеров объявить: «Больше никаких имитаций!» Но правильное понимание ограничений фиктивных объектов может помочь вам избежать их неправильного использования. Подробнее о работе с фиктивными объектами в главе 13. Если хрупкие тесты выполняются некорректно, то крупный набор тестов — медлен- но. Чем дольше работает набор тестов, тем реже он будет запускаться и тем меньше преимуществ он даст. Мы используем некоторые приемы для ускорения выполнения нашего набора тестов, включая параллельное выполнение и использование более быстрого оборудования. Тем не менее эти уловки нивелируются большим количе- ством медленных тестов. Медленная работа тестов может быть обусловлена множеством причин, таких как длительная загрузка фрагментов системы, запуск эмулятора перед выполнением, обработка больших наборов данных или ожидание синхронизации разрозненных систем. Нередко тесты сначала работают достаточно быстро, но потом замедляются по мере роста системы. Например, интеграционный тест, проверяющий одну зави- симость за пять секунд, при появлении новых зависимостей от десятков сторонних служб станет выполняться пять минут. 224 Глава 11. Основы тестирования Тесты также могут замедляться из-за ненужных ограничений скорости, устанав- ливаемых такими функциями, как sleep() и setTimeout() . Эти функций часто используются в качестве наивных эвристик перед проверкой результата недетер- минированного поведения. Ожидание по полсекунды то здесь, то там сначала не кажется опасным, но если в широко используемую утилиту встроено «ожидание с последующей проверкой», то довольно скоро в тестовом прогоне появятся минуты простоя. Лучшим решением этой проблемы является активный опрос для опреде- ления изменения состояния с частотой, измеряемой микросекундами. Этот прием можно объединить с тайм-аутом на тот случай, если в ходе тестирования не удастся достичь стабильного состояния. Недетерминированные и медленные наборы тестов препятствуют повышению про- дуктивности инженера. Столкнувшись с такими тестами, инженеры Google стали искать способы обойти замедление, но некоторые зашли так далеко, что вообще перестали проводить тестирование перед отправкой изменений. Очевидно, что это рискованная практика и ее следует избегать, но если набор тестов приносит больше вреда, чем пользы, то инженеры найдут способ выполнить свою работу, с тестиро- ванием или без него. Но большой набор тестов может быть очень полезен, если относиться к нему с ува- жением. Мотивируйте инженеров заботиться о своих тестах, вознаграждайте их за надежные тесты в такой же мере, как за отличную реализацию некоторой особенно- сти. Определите соответствующие нормативы производительности и способствуйте рефакторингу медленных или ненадежных тестов. Относитесь к своим тестам как к продакшену. Когда простые изменения начинают занимать значительное время, приложите все силы, чтобы сделать тесты менее хрупкими. Уделяйте внимание не только культуре, но и инфраструктуре тестирования. Раз- рабатывайте инструменты статического анализа, пишите документацию и оказы- вайте любую другую помощь в повышении качества тестов. Сократите количество фреймворков и инструментов, которые вам нужно поддерживать, чтобы выделить время на усовершенствование тестов 1 . Если вы не будете уделять внимания простоте управления тестами, инженеры рано или поздно решат, что нет смысла тратить время и силы на их разработку. |