Делай как вGoogle
Скачать 5.77 Mb.
|
Тестирование со скоростью современной разработки Программные системы становятся все больше и сложнее. Типичное приложение или служба в Google состоит из тысяч или миллионов строк кода, использует сотни библиотек или фреймворков и доставляется через ненадежные сети на все большее число платформ с разными настройками. Что еще хуже, новые версии приложения рассылаются пользователям порой по нескольку раз в день. Наша современность сильно отличается от эпохи коробочных программных продуктов, которые обнов- лялись пару раз в год. Механизмы ручного тестирования поведения системы не отвечают стремительному росту числа функций и поддерживаемых платформ в большинстве программных продуктов. Представьте, что вам нужно вручную проверить все функциональные возможности Google Search, такие как поиск авиарейсов, киносеансов, релевантных изображений и, конечно же, веб-поиск (рис. 11.1). Даже если вы найдете решение этой задачи, умножьте получившуюся нагрузку на количество языков, стран и устройств, которые должны поддерживаться в Google Search, и не забудьте учесть такие аспекты, как доступность продукта для людей с ограниченными возможностями и безопас- ность. Подходы, основанные на оценке качества продукта путем ручного взаимодей- ствия с каждой функцией, просто не масштабируются. Поэтому, когда дело доходит до тестирования, есть только одно решение: автоматизация. Пиши, запускай, исправляй В чистом виде автоматическое тестирование состоит из трех этапов: написание тестов, их запуск и исправление выявленных ошибок. Автотест — это небольшой фрагмент кода, обычно отдельная функция или метод, вызывающий изолированную часть более крупной системы, которую требуется протестировать. Тест настраивает среду для тестирования, вызывает систему, обычно с известными входными данными, и проверяет результат. Некоторые тесты очень маленькие и имеют единственный путь выполнения; другие — намного больше и могут включать в себя целые системы, такие как мобильная ОС или веб-браузер. В примере 11.1 представлен простой тест на Java, не использующий специализирован- ных фреймворков или библиотек. Он не является примером целого набора тестов, но каждый автоматизированный тест по своей сути очень похож на этот простой пример. В отличие от старых процессов контроля качества, при которых целые команды спе- циально нанятых тестировщиков исследовали новые версии программных систем, современные инженеры играют самую активную и непосредственную роль в напи- сании автоматических тестов для своего кода и их выполнении. Даже в компаниях, где подразделения контроля качества занимают важное место, тесты, написанные разработчиками, являются обычным явлением. При скорости и охвате современной 210 Глава 11. Основы тестирования разработки единственный способ не отстать — организовать написание тестов всеми инженерами. Рис. 11.1. Скриншоты с комплексными результатами в Google Search Почему мы пишем тесты? 211 Пример 11.1. Пример теста // Проверяет обработку отрицательных результатов классом Calculator public void main(String[] args) { Calculator calculator = new Calculator(); int expectedResult = -3; int actualResult = calculator.subtract(2, 5); // Из 2 вычесть 5 assert(expectedResult == actualResult); } Конечно, написание тестов отличается от написания хороших тестов. Обучить десят- ки тысяч инженеров писать хорошие тесты — довольно сложная задача, и в следую- щих главах мы расскажем все, что нам удалось узнать о написании хороших тестов. Написание тестов — это только первый шаг в процессе автоматизированного те- стирования. После того как тесты будут написаны, их нужно запускать, причем часто. Фактически автоматизированное тестирование заключается в многократном повторении одного и того же действия и требует внимания человека только при поломке. Мы обсудим непрерывную интеграцию и тестирование в главе 23. Вы- ражая тесты в виде кода, а не последовательности шагов, выполняемых вручную, мы получаем возможность запускать их после каждого изменения кода хоть ты- сячи раз в день. В отличие от людей-тестировщиков машины никогда не устают и не отвлекаются. Еще одно преимущество использования тестов в виде кода — их легко адаптировать к различным средам. Тестирование поведения Gmail в Firefox требует не больше уси- лий, чем его тестирование в Chrome, при наличии в тесте конфигурации для обеих этих систем 1 . Код, тестирующий пользовательский интерфейс на английском языке, с не меньшим успехом может тестировать интерфейсы на японском или немецком. Тестирование продуктов и служб, находящихся в процессе активной разработки, не- избежно будет сталкиваться с проблемами. Эффективность процесса тестирования во многом будет зависеть от реакции инженеров на появление этих проблем. Игно- рирование неудачных тестов быстро лишит их любой ценности, которую они давали, поэтому крайне важно отнестись к ним серьезно. Команды, уделяющие приоритетное внимание исправлению сломанного теста в течение нескольких минут после обнару- жения сбоя, способны поддерживать высокую степень уверенности в тесте, быстро выяснять причины ошибок и, следовательно, получать большую выгоду от тестов. Таким образом, здоровая культура автоматизированного тестирования поощряет участие всего коллектива в написании тестов и гарантирует их регулярное выпол- нение. Что еще важнее, она подразумевает быстрое исправление сломанных тестов, чтобы поддерживать высокую степень доверия к тестированию. 1 Правильное поведение в разных браузерах и в пользовательских интерфейсах на разных языках — это отдельная история! Но в идеале все конечные пользователи продукта должны получить одинаковый опыт. 212 Глава 11. Основы тестирования Преимущества тестирования кода Разработчикам из организаций, в которых нет устойчивой культуры тестирования, рассуждения о написании тестов как о средстве увеличения продуктивности и ско- рости разработки могут показаться странными. В конце концов, на написание тестов может уйти столько же времени (и даже больше!), что и на реализацию тестируемой функции. Однако это не так, мы в Google обнаружили, что затраты на тестирование дают ряд важнейших преимуществ, сказывающихся на продуктивности разработ- чиков: Уменьшение числа отладок Тестирование уменьшает число дефектов в коде, причем не только перед его отправкой в репозиторий. Код в Google может меняться десятки раз другими командами и автоматизированными системами сопровождения. Тест, написан- ный один раз, предотвращает появление дорогостоящих дефектов и избавляет инженеров от раздражающих сеансов отладки в течение всего жизненного цикла проекта. Изменения в проекте или в его зависимостях, вызывающие ошибку при выполнении тестов, будут быстро обнаружены инфраструктурой тестирования и отменены до того, как ошибка попадет в продакшен. Уверенность в изменениях Любое ПО рано или поздно меняется. Команды с надежными тестами могут с уверенностью вносить изменения в свой проект, потому что все важные функ- ции в их проектах постоянно проверяются. В тестируемых проектах проще про- водить рефакторинг, потому что измененный код, сохранивший существующее поведение (в идеале), не должен требовать изменений в существующих тестах. Документирование Документация по ПО заведомо ненадежна. Она может быть слабо связанной с кодом, устаревшей или неполной. Четкие целенаправленные тесты, каждый из которых проверяет что-то одно, действуют подобно документам: чтобы узнать, что делает код в конкретном случае, загляните в тест, проверяющий этот случай. Когда требования меняются и новый код вызывает ошибку при выполнении существующего теста — это сигнал о том, что «документация» устарела. Обра- тите внимание, что тесты действуют как документы, только если сохранены их четкость и краткость. Простота обзора Весь код в Google просматривается, по крайней мере, еще одним инженером перед отправкой в репозиторий (подробнее об обзорах кода в главе 9). Рецен- зент тратит меньше усилий на проверку работоспособности кода, если код сопровождается тщательно сконструированными тестами, которые проверяют правильность кода, обработку худших случаев и ошибки. Вместо кропотливого и утомительного исследования каждого возможного случая рецензент может просто посмотреть, имеются ли тесты для всех случаев и выполняются ли они успешно. Проектирование набора тестов 213 Продуманность дизайна Написание тестов для нового кода помогает проверить практичность API, реализуемого этим кодом. Если новый код сложен в тестировании, часто это обус ловлено присутствием в коде слишком широких обязанностей или трудно- управляемых зависимостей. Хорошо продуманный код должен быть модульным, не образовывать тесных связей и фокусироваться на конкретных обязанностях. Устранение проблем с дизайном на ранних этапах часто избавляет от лишней работы в последующем. Частота выхода и высокое качество новых версий С помощью наборов автоматизированных тестов команды могут уверенно вы- пускать новые версии своих приложений. Даже крупные проекты в Google с сот- нями инженеров выпускают новые версии продуктов каждый день. Это было бы невозможно без автоматического тестирования. Проектирование набора тестов Основы нашего подхода к тестированию были заложены давно. С ростом нашей кодовой базы методом проб и ошибок мы многое узнали о проектировании и вы- полнении наборов тестов. Уже на самых ранних этапах внедрения тестирования мы обнаружили, что инжене- ры предпочитали писать тесты, охватывающие всю систему, но эти тесты работали медленно, были ненадежными и сложными в отладке. И инженеры задались вопро- сами: «Почему мы не тестируем серверы по одному?» и «Зачем тестировать сервер целиком, когда проще тестировать его небольшие модули по отдельности?» В конце концов, стремление избавиться от сложностей привело к тому, что команды стали разрабатывать все меньшие и меньшие тесты, которые работали быстрее и стабильнее и были проще в отладке. Это вызвало многочисленные споры о значении слова «маленький». Можно ли считать юнит-тест маленьким? А как определить размер интеграционного теста? Мы пришли к выводу, что каждый тест имеет два разных измерения: размер и охват. Размер определяется объемом ресурсов, необходимых для запуска теста, таких как память, процессы и время. Под охватом понимаются конкретные пути в коде, про- веряемые тестом. Обратите внимание, что выполнение строки кода не является проверкой правильности его поведения. Размер и охват — взаимосвязанные, но все же разные понятия. Размер теста Мы в Google классифицируем тесты по размеру и призываем инженеров всегда пи- сать минимально возможные тесты для проверки некой единицы функциональности. Размер теста определяется не количеством строк кода, а тем, как он выполняется, что ему разрешено делать и сколько ресурсов он потребляет. Фактически наше деле- 214 Глава 11. Основы тестирования ние на маленькие, средние и большие тесты определяется ограничениями, которые инфраструктура тестирования может наложить на тест. Подробнее мы рассмотрим эти ограничения ниже, а пока отметим, что маленькие тесты выполняются в одном процессе, средние тесты — на одной машине, а большие тесты могут охватывать столько машин и процессов, сколько понадобится (рис. 11.2) 1 Маленький Средний Большой Один процесс Одна машина Рис. 11.2. Размеры тестов Такая классификация, в отличие от более традиционного деления на «юнит-тесты» или «интеграционные тесты», была выбрана, потому что для нас важнее такие качества теста, как скорость выполнения и детерминированность независимо от широты охвата тестирования. Маленькие тесты с любой широтой охвата за счет ограничений, наложенных на них, почти всегда быстрее и более детерминированы, чем тесты, вовлекающие в работу обширную инфраструктуру или потребляющие больше ресурсов. С увеличением размеров тестов многие ограничения ослабляются. Средние тесты обладают большей гибкостью, но они недостаточно детерминированы. Большие тесты сохраняются только для самых сложных и комплексных сценариев тестирования. Давайте подробнее рассмотрим, какие ограничения накладываются на каждый тип тестов. Маленькие тесты Маленькие тесты имеют больше всего ограничений. Основное ограничение — они должны выполняться в том же процессе (а во многих языках — в том же потоке), что и тестируемый код. Это значит, что нельзя запустить сервер и подключить к нему 1 Строго говоря, мы в Google различаем четыре размера тестов: маленький, средний, большой и огромный. Разница между большими и огромными тестами на самом деле очень тонкая и корнями уходит глубоко в историю. Понятие «большой» в этой книге в основном соот- ветствует нашему понятию «огромный». Проектирование набора тестов 215 отдельный процесс с маленьким тестом, а в ходе выполнения маленького теста нельзя запустить стороннюю программу, такую как база данных. Также при выполнении маленьких тестов запрещены остановки, операции ввода/ вывода 1 и любые другие блокирующие вызовы. То есть маленькие тесты не должны обращаться к сети или диску. Для тестирования кода, полагающегося на эти запре- щенные операции, необходимо использовать тестовые дублеры (глава 13), в которых обременительные зависимости замещены внутрипроцессными реализациями. Цель этих ограничений — отобрать у маленьких тестов доступ к источникам медли- тельности и недетерминированности. Тест, действующий в единственном процессе и не использующий блокирующие вызовы, сможет эффективно выполняться с мак- симальной скоростью, на какую только способен процессор. Случайно сделать такой тест медленным или недетерминированным трудно (хотя возможно). Ограничения, накладываемые на маленькие тесты, формируют безопасную среду, которая не по- зволяет инженерам выстрелить себе в ногу. Эти ограничения могут показаться чрезмерными, но представьте довольно скромный набор из пары сотен маленьких тестов, многократно запускаемый в течение дня. Если некоторые из этих тестов будут терпеть неудачу случайным (недетерминиро- ванным) образом (часто такие тесты называют нестабильными (flaky tests), https:// oreil.ly/NxC4A ), на выявление причин неудач потребуется слишком много времени, что отрицательно скажется на продуктивности инженера. В масштабах Google такая проблема может привести к остановке всей инфраструктуры тестирования. Мы в Google призываем инженеров писать маленькие тесты, когда это возможно, независимо от широты охвата тестирования, потому что такой подход обеспечивает быстрый и надежный запуск всего набора тестов. Подробнее о маленьких тестах и юнит-тестах — в главе 12. Средние тесты Ограничения, накладываемые на маленькие тесты, могут оказаться слишком жест- кими для многих интересных видов тестирования. Следующая ступенька вверх по лестнице размеров — средние тесты. Средние тесты могут действовать в нескольких процессах, использовать механизмы многопоточного выполнения и производить блокирующие вызовы, включая сетевые вызовы к localhost . Единственное ограни- чение — средним тестам не разрешается посылать сетевые вызовы к любым другим системам, кроме localhost . То есть средний тест не должен пересекать границ одной машины. Разрешение использовать несколько процессов открывает массу новых возможностей для тестирования. Например, тест может запустить экземпляр базы данных, чтобы убедиться, что тестируемый код правильно интегрируется в более реалистичной среде, или проверить взаимодействие веб-интерфейса с серверным кодом. Тесты 1 Но есть определенное пространство для маневра. Тестам разрешается обращаться к файловой системе при условии использования герметичной реализации в памяти. 216 Глава 11. Основы тестирования для веб-приложений часто используют такие инструменты, как WebDriver ( https:// oreil.ly/W27Uf ), которые запускают настоящий браузер и управляют им удаленно из процесса, в котором действует тест. К сожалению, с увеличением гибкости теста снижаются его скорость выполнения и детерминированность. Тесты, действующие в нескольких процессах или выпол- няющие блокирующие вызовы, зависят от ОС и других процессов, которые должны работать быстро и детерминированно, чего мы не можем гарантировать. Средние тесты сохраняют некоторую защищенность, запрещая доступ к удаленным машинам по сети, который делает тесты медлительными и недетерминированными. Но «без- опасность» в средних тестах почти отсутствует, поэтому инженеры должны работать с ними осторожно. ПРИМЕР: НЕСТАБИЛЬНЫЕ ТЕСТЫ ОБХОДЯТСЯ ДОРОГО При наличии нескольких тысяч тестов, многократно выполняющихся в течение дня, каждый из которых может изредка проявлять нестабильность в работе, какой-то из них почти наверняка будет терпеть неудачу (демонстрировать нестабильное поведение). С уве- личением количества запусков тестов статистически будет увеличиваться и количество неудачных тестовых прогонов. Если вероятность случайной неудачи для каждого такого теста будет составлять всего 0,1 % и в течение дня выполняется 10 000 тестов, вам придется расследовать 10 случаев отказа в день. Каждое расследование отнимает время, которое можно было бы потратить с большей пользой. В некоторых случаях можно ограничить влияние нестабильных тестов, автоматически перезапуская их в случае неудачи. Это позволяет обменять время инженера на такты процессора. Такой компромисс имеет смысл при низкой вероятности нестабильного по- ведения. Но имейте в виду, что повторный запуск тестов откладывает на потом устранение причины нестабильности. Если нестабильность тестов продолжит увеличиваться, вы столкнетесь с чем-то намного худшим, чем потеря продуктивности инженера, — с потерей уверенности в тестах. Неста- бильность не успеет вырасти значительно, прежде чем команда потеряет доверие к набору тестов. И когда это произойдет, инженеры перестанут реагировать на возникающие неудачи, что полностью сведет на нет все выгоды, которые дает набор тестов. Наш опыт показывает, что при приближении к уровню нестабильности в 1 % тесты начинают терять свою цен- ность. У нас в Google вероятность нестабильности составляет около 0,15 %, что означает тысячи ложных ошибок каждый день. Мы упорно стараемся держать нестабильность под контролем, в том числе выделяя инженерам время для устранения ее причин. Часто случайные неудачи тестовых прогонов объясняются недетерминированным по- ведением самих тестов. В ПО есть множество источников недетерминированности: время такта, особенности планирования потоков выполнения, задержки в сети и т. д. Научиться изолировать и стабилизировать влияние случайных эффектов нелегко. Иногда они обусловлены низкоуровневыми проблемами, такими как аппаратные прерывания или особенности работы механизма отображения в браузере. Хорошая инфраструктура автома- тизированного тестирования должна помогать инженерам выявлять недетерминированное поведение и ослаблять его влияние на результаты тестирования. |