Текст книги "Как тестируют в Google"
Автор книги: Джеймс Уиттакер
Соавторы: Джефф Каролло,Джейсон Арбон
Жанр:
Программирование
сообщить о нарушении
Текущая страница: 7 (всего у книги 26 страниц)
Определения размеров тестов
По мере роста Google и прихода новых сотрудников в компании началась путаница с названиями тестов: юнит-тесты, тесты на основе кода, тесты белого ящика, интеграционные тесты, системные тесты и сквозные тесты – все они выделяли разные уровни детализации, как рассказывает Пэм на рис. 2.1. Однажды мы решили, что так дальше продолжаться не может, и создали стандартный набор типов тестов.
Малые тестыпроверяют работу каждой единицы кода независимо от ее окружения. Примеры таких единиц кода: отдельные классы или небольшие группы связанных функций. У малых тестов не должно быть внешних зависимостей. Вне Google такие малые тесты обычно называют юнит-тестами.
У малых тестов самый узкий охват, и они фокусируются на одной, отделенной от всего, функции, как показано на рис. 2.2 на следующей странице. Такой узкий охват малых тестов позволяет им обеспечивать исчерпывающее покрытие низкоуровневого кода, недоступное для более крупных тестов.
Рис. 2.1. В Google используется много разных видов тестов
Рис. 2.2. В малом тесте обычно проверяется всего одна функция
Рис. 2.3. Средние тесты охватывают несколько модулей и могут задействовать внешние источники данных
Рис. 2.4. Большие и громадные тесты включают модули, необходимые для сквозного выполнения задач
Для малых тестов необходимо имитировать внешние сервисы вроде файловой системы, сетей и баз данных через подставные объекты и имитации. Лучше имитировать даже внутренние сервисы, которые находятся внутри того же модуля, что и тестируемый класс. Чем меньше внешних зависимостей – тем лучше для малых тестов.
Ограниченный охват и отсутствие внешних зависимостей означают, что малые тесты могут работать очень быстро. Следовательно, их можно часто запускать и быстро находить ошибки. Задумка в том, чтобы разработчик, по мере запуска тестов и правки основного кода, заодно отвечал за поддержку этого тестового кода. Изоляция малых тестов также позволяет сократить время их сборки и исполнения.
Средние тестыпроверяют взаимодействие между двумя или более модулями приложения, как это показано на рис. 2.3. Средние тесты отличаются от малых большим охватом и временем прогона. Если малые тесты пытаются задействовать весь код одной функции, средние тесты направлены на взаимодействие между определенным набором модулей. За пределами Google такие тесты обычно называют интеграционными.
Средними тестами нужно управлять через тестовую инфраструктуру. Из-за большего времени прогона они запускаются реже. В основном эти тесты создаются и выполняются силами разработчиков в тестировании.
На заметку
Малые тесты проверяют поведение отдельной единицы кода. Средние тесты проверяют взаимодействие одного или нескольких модулей кода. Большие тесты проверяют работоспособность системы в целом.
Имитация внешних сервисов для средних тестов приветствуется, но не обязательна. Это может быть полезно, если нужно увеличить быстродействие. Там, где полноценная имитация сервисов неоправданна, для повышения производительности можно использовать облегченные вариации, например встроенные в память базы данных.
Большие и громадные тестыза пределами Google называют системными тестами, или сквозными тестами. Большие тесты оперируют на высоком уровне и проверяют, что система работает как единое целое. Эти тесты задействуют все подсистемы, начиная с пользовательского интерфейса и заканчивая хранилищами данных, как это показано на рис. 2.4. Они могут обращаться к внешним ресурсам, таким как базы данных, файловые системы и сетевые службы.
Как мы используем размеры тестов в общей инфраструктуре
Автоматизацию тестирования трудно сделать универсальной. Чтобы все проекты в большой IT-компании могли работать с общей тестовой инфраструктурой, она должна поддерживать множество разных сценариев запуска тестов.
Например, вот некоторые типичные сценарии запуска тестов, которые поддерживает общая инфраструктура тестирования Google.
– Разработчик хочет скомпилировать и запустить малый тест и тут же получить результаты.
– Разработчик хочет запустить все малые тесты для проекта и тут же получить результаты.
– Разработчик хочет скомпилировать и запустить только те тесты, которые связаны с последним изменением кода, и тут же получить результаты.
– Разработчик или тестировщик хочет собрать данные о покрытии кода в конкретном проекте и посмотреть результаты.
– Команда хочет прогонять все малые тесты для своего проекта каждый раз при создании списка изменений и рассылать результаты всем участникам команды.
– Команда хочет прогонять все тесты для своего проекта после отправки списка изменений в систему управления версиями.
– Команда хочет еженедельно собирать статистику о покрытии кода и отслеживать его прогресс со временем.
Может быть и так, что все вышеперечисленные задания отправляются в систему выполнения тестов Google одновременно. Некоторые из тестов могут захватывать ресурсы, занимая общие машины на целые часы. Другим будет достаточно миллисекунд для выполнения, и они могут благополучно исполняться на одной машине с сотнями других тестов. Когда тесты помечены как малые, средние и большие, гораздо проще планировать расписание выполнения запусков, так как планировщик понимает, сколько времени может занять запуск, и оптимизирует очередь.
Система выполнения тестов Google отличает быстрые задания от медленных по информации о размере тестов. У каждого размера есть верхняя граница времени выполнения теста (табл. 2.1). Размер определяет и потенциальную потребность в ресурсах (табл. 2.2). Система прерывает выполнение и сообщает об ошибке, если тест превышает выделенное для его категории время или доступный объем ресурса. Это мотивирует разработчиков в тестировании назначать правильные метки размеров тестов. Точное определение размеров тестов позволяет системе строить эффективное расписание.
Преимущества разных размеров тестов
Размер теста имеет значение. Он влияет на специфические преимущества теста. На рис. 2.5 показана общая сводка, а ниже мы приводим более подробный список достоинств и недостатков каждого типа тестов.
Рис. 2.5. Ограничения разных размеров тестов
Большие тесты
Достоинства и недостатки больших тестов:
– Большие тесты проверяют самое важное – работу приложения. Они учитывают поведение внешних подсистем.
– Большие тесты могут быть недетерминированными (результат может быть получен разными путями), потому что зависят от внешних подсистем.
– Большой охват усложняет поиск причин при неудачном прохождении теста.
– Подготовка данных для тестовых сценариев может занимать много времени.
– Из-за высокоуровневости больших тестов в них трудно прорабатывать граничные значения. Для этого нужны малые тесты.
Средние тесты
Достоинства и недостатки средних тестов:
– Требования к подставным объектам мягче, а временные ограничения свободнее, чем у малых тестов. Разработчики используют их как промежуточную ступень для перехода от больших тестов к малым.
– Средние тесты выполняются относительно быстро, поэтому разработчики могут запускать их часто.
– Средние тесты выполняются в стандартной среде разработки, поэтому их очень легко запускать.
– Средние тесты учитывают поведение внешних подсистем.
– Средние тесты могут быть недетерминированными, потому что зависят от внешних подсистем.
– Средние тесты выполняются не так быстро, как малые.
Малые тесты
Достоинства и недостатки малых тестов:
– Малые тесты помогают повысить чистоту кода, потому что работают узконаправленно с небольшими методами. Соблюдение требований подставных объектов приводит к хорошо структурированным интерфейсам между подсистемами.
– Из-за скорости выполнения малые тесты выявляют баги очень рано и дают немедленную обратную связь при внесении изменений в код.
– Малые тесты надежно выполняются во всех средах.
– Малые тесты обладают большей детализацией, а это упрощает тестирование граничных случаев и поиск состояний, приводящих к ошибкам, например null-указатели.
– Узкая направленность малых тестов сильно упрощает локализацию ошибок.
– Малые тесты не проверяют интеграцию между модулями – для этого используются другие тесты.
– Иногда сложно применить подставные объекты для подсистем.
– Подставные объекты и псевдосреды могут отличаться от реальности.
Малые тесты способствуют созданию качественного кода, хорошей проработке исключений и получению информации об ошибках. Более масштабные тесты ориентированы на общее качество продукта и проверку данных. Ни один тип тестов не покрывает все потребности продукта в тестировании. Поэтому в проектах Google мы стараемся использовать разумное сочетание всех типов тестов в каждом тестовом наборе. Автоматизация, основанная только на больших комплексных тестах, так же вредна, как и создание только малых юнит-тестов.
На заметку
Малые тесты направлены на проверку качества кода, а средние и большие – на проверку качества всего продукта.
Покрытие кода —отличный инструмент, чтобы оценить, насколько разумно используется сочетание разных размеров тестов в проекте. Проект генерирует один отчет с данными покрытия только для малых тестов, а потом другой отчет с данными только для средних и больших тестов. Каждый отчет в отдельности должен показывать приемлемую величину покрытия для проекта. Если средние и большие тесты в отдельности обеспечивают только 20-процентное покрытие, а покрытие малыми тестами приближается к 100, то у проекта не будет доказательств работоспособности всей системы. А если поменять эти числа местами, скорее всего, расширение или сопровождение проекта потребует серьезных затрат на отладку. Чтобы генерировать и просматривать данные о покрытии кода на ходу, мы используем те же инструменты, которые собирают и выполняют тесты. Достаточно поставить дополнительный флаг в командной строке. Данные о покрытии кода хранятся в облаке, и любой инженер может просмотреть их через веб в любой момент.
Google разрабатывает самые разные проекты, их потребности в тестировании сильно отличаются. В начале работы мы обычно используем правило 70/20/10: 70% малых тестов, 20% – средних и 10% – больших. В пользовательских проектах со сложными интерфейсами или высокой степенью интеграции доля средних и крупных тестов должна быть выше. В инфраструктурных проектах или проектах, где много обработки данных (например, индексирование или обход веб-контента), малых тестов нужно намного больше, чем больших и средних.
Для наблюдения за покрытием кода в Google используется внутренний инструмент – Harvester. Это инструмент визуализации, который отслеживает все списки изменений проекта и графически отображает важные показатели: отношение объема кода тестов к объему нового кода в конкретных списках изменений; размер изменений; зависимость частоты изменений от времени и даты; распределение изменений по разработчикам и т.д. Цель Harvester – дать общую сводку об изменениях в процессе тестирования проекта со временем.
Требования к выполнению тестов
У системы выполнения тестов в Google одинаковые требования ко всем тестам.
– Каждый тест должен быть независим от других, чтобы тесты могли выполняться в любом порядке.
– Тесты не должны иметь долгосрочных последствий. После их завершения среда должна возвращаться в то же состояние, в котором она находилась при запуске.
Требования простые и понятные, но выполнить их оказывается не так просто. Даже если сам тест отвечает требованиям, тестируемая программа может их нарушать, сохраняя файлы данных или изменяя конфигурацию. К счастью, сама среда выполнения тестов Google упрощает соблюдение этих требований.
Что касается требования независимости, инженер во время прогона может установить флаг выполнения тестов в случайном порядке. Эта фича помогает выявить зависимости, связанные с порядком выполнения. Впрочем, случайный порядок может означать, что тесты запускаются параллельно. Система может отправить выполнять два теста на одной машине. Если каждый тест требует единоличного доступа к ресурсам системы, один из них упадет. Например:
– оба теста пытаются подключиться к одному порту для единоличного получения сетевого трафика;
– оба теста пытаются создать каталог, используя один путь;
– один тест создает и заполняет таблицу базы данных, а другой пытается удалить ту же таблицу.
Такие конфликты могут вызывать сбои не только в самих тестах, но и в соседних тестах, которые выполняются в той же системе, даже если эти другие тесты соблюдают правила. Наша система умеет выявлять такие ситуации и оповещать владельцев тестов-бунтарей.
Если установить специальный флаг, тест будет выполняться единолично на выделенной машине. Но это лишь временное решение. Все равно придется переписать тесты и удалить зависимости от критических ресурсов. Например, эти проблемы можно решить так:
– каждый тест запрашивает свободный порт у системы выполнения тестов, а тестируемая программа динамически к нему подключается;
– каждый тест создает все папки и файлы во временной директории, созданной и выделенной системой специально для него перед выполнением тестов;
– каждый тест работает со своим экземпляром базы данных в изолированной среде с выделенными системой выполнения тестов директориями и портами.
Ребята, ответственные за сопровождение системы выполнения тестов Google, довольно подробно описали свою среду выполнения тестов. Их документ называется «Энциклопедией тестирования Google», и он отвечает на все вопросы о том, какие ресурсы доступны тестам во время выполнения. «Энциклопедия тестирования» составлена как стандартизированный документ, где у терминов «должен» и «будет» однозначное значение. В энциклопедии подробно объясняются роли и обязанности тестов, исполнителей тестов, систем хостинга, рантайм-библиотек, файловых систем и т.д.
Вряд ли все инженеры Google читали «Энциклопедию тестирования». Скорее всего, большинство предпочитает учиться у других, или испытывать метод проб и ошибок, или постоянно натыкаться на комментарии рецензентов их кода. Они и не подозревают, что общая среда выполнения тестов может обслужить все проекты по тестированию Google. Чтобы это узнать, достаточно заглянуть в энциклопедию. Им неизвестно, что этот документ – главная причина того, что тесты ведут себя в общей среде ровно так же, как и на личной машине написавшего тест инженера. Технические детали даже самых сложных систем остаются незамеченными теми, кто их использует. Все же работает, зачем читать.
Тестирование на скоростях и в масштабах Google
Пуджа Гупта, Марк Айви и Джон Пеникс
Системы непрерывной интеграции – главные герои обеспечения работоспособности программного продукта во время разработки. Типичная схема работы большинства систем непрерывной интеграции такая.
1. Получить последнюю копию кода.
2. Выполнить все тесты.
3. Сообщить о результатах.
4. Перейти к пункту 1.
Решение отлично справляется с небольшой кодовой базой, пока динамичность изменений кода не выходит за рамки, а тесты прогоняются быстро. Чем больше становится кода, тем сильнее падает эффективность подобных систем. Добавление нового кода увеличивает время «чистого» запуска, и в один прогон включается все больше изменений. Если что-то сломается, найти и исправить изменение становится все сложнее.
Разработка программных продуктов в Google происходит быстро и с размахом. Мы добавляем в базу кода всего Google больше 20 изменений в минуту, и 50% файлов в ней меняются каждый месяц. Разработка и выпуск всех продуктов опираются на автотесты, проверяющие поведение продукта. Есть продукты, которые выпускаются несколько раз в день, другие – раз в несколько недель.
По идее, при такой огромной и динамичной базе кода команды должны тратить кучу времени только на поддержание сборки в состоянии «зеленого света». Система непрерывной интеграции должна помогать с этим. Она должна сразу выделять изменение, приводящее к сбою теста, а не просто указывать на набор подозрительных изменений или, что еще хуже, перебирать их все в поисках нарушителя.
Чтобы решить эту проблему, мы построили систему непрерывной сборки (рис. 2.6), которая анализирует зависимости и выделяет только те тесты, которые связаны с конкретным изменением, а потом выполняет только их. И так для каждого изменения. Система построена на инфраструктуре облачных вычислений Google, которая позволяет одновременно выполнять большое количество сборок и запускать затронутые тесты сразу же после отправки изменений.
Примером ниже мы показываем, как наша система дает более быструю и точную обратную связь, чем типичная непрерывная сборка. В нашем сценарии используются два теста и три изменения, затрагивающие эти тесты. Тест gmail_server_tests падает из-за изменения 2. Типичная система непрерывной сборки сообщила бы, что к сбой случился из-за изменения 2 или 3, не уточняя. Мы же используем механизм параллельного выполнения, поэтому запускаем тесты независимо, не дожидаясь завершения текущего цикла «сборка–тестирование». Анализ зависимостей сузит набор тестов для каждого изменения, поэтому в нашем примере общее количество выполнений теста то же самое.
Рис. 2.6. Сравнение систем непрерывной интеграции
Наша система берет данные о зависимостях из спецификаций сборки, которые описывают, как компилируется код и какие файлы входят в сборку приложения и теста. Правила сборки имеют четкие входные и выходные данные, объединив которые получим точное описание процесса сборки. Наша система строит в памяти график зависимостей сборки, как на рис. 2.7, и обновляет его с каждым новым изменением. На основании этой схемы мы определяем все тесты, связанные прямо или косвенно с кодом, вошедшим в изменение. Именно эти тесты нужно запустить, чтобы узнать текущее состояние сборки. Давайте посмотрим на пример.
Рис. 2.7. Пример зависимостей сборки
Мы видим, как два отдельных изменения в коде, находящихся на разных уровнях дерева зависимостей, анализируются, чтобы подобрать минимальный набор тестов, который определит, дать ли зеленый свет проектам Gmail и Buzz.
Сценарий 1: изменение в общей библиотеке
Для первого сценария возьмем изменение, которое модифицирует файлы в common_collections_util, как показано на рис. 2.8.
Рис. 2.8. Изменение в common_collections_util.h
Отправив изменение, мы перемещаемся по линиям зависимостей вверх по графику. Так мы найдем все тесты, зависящие от изменений. Когда поиск завершится, а это займет лишь доли секунды, у нас будут все тесты, которые нужно прогнать, и мы получим актуальные статусы наших проектов (рис. 2.9).
Рис. 2.9. Тесты, на которые влияет изменение
Сценарий 2: изменение в зависимом проекте
Во втором примере возьмем изменение, которое модифицирует файлы в youtube_client (рис. 2.10).
Рис. 2.10. Изменение в youtube_client
Проведя аналогичный анализ, мы определим, что изменение влияет только на buzz_client_tests и что нужно актуализировать статус проекта Buzz (рис. 2.11).
Рис. 2.11. Buzz нужно обновить
Примеры показывают, как мы оптимизируем количество тестов, прогоняемых для одного изменения, без потери в точности результатов. Уменьшение количества тестов для одного изменения позволяет выполнить всенужные тесты для каждого зафиксированного изменения. Нам становится легче выявлять и отлаживать проблемы в проблемном изменении.
Умные инструменты и возможности инфраструктуры облачных вычислений сделали систему непрерывной интеграции быстрой и надежной. И мы постоянно стараемся ее улучшить, хотя она уже используется в тысячах проектов Google, чтобы выпускать проекты быстрее и проводить больше итераций. И – что важно – наш прогресс замечают пользователи.
Тест-сертификация
В начале книги Патрик Коупленд замечает, как сложно было привлечь разработчиков к тестированию. Первым делом мы создали им отличную компанию и наняли технически подкованных тестировщиков. А чтобы втянуть разработчиков в процесс, мы придумали «Тест-сертификацию». Оглядываясь назад, можно сказать, эта программа сыграла важную роль в становлении культуры тестирования разработчиками в Google.
Тест-сертификация начиналась как соревнование. Будут ли разработчики серьезно относиться к тестированию, если мы сделаем эту работу престижной? Что, если награждать разработчиков, которые следуют тестовым практикам? А что, если мы скажем, что они теперь сертифицированные инженеры? А может, еще ввести систему наградных бейджей (рис. 2.12), которыми можно пощеголять перед коллегами?
Рис. 2.12. Бейджи тест-сертификации показываются на вики-страницах проектов
Мы изобрели тест-сертификацию – это система заданий по тестированию, которые должна выполнить команда, чтобы стать сертифицированной. Все команды начинают с нулевого уровня. Если команда показывает мастерство базовой гигиены кода, ей дается первый уровень. Уровень команды постепенно растет с тем, как она учится писать все более чистый код. В игре в сертификацию всего пять уровней, как и во многих серьезных моделях зрелости разработки ПО.
Краткое описание уровней Тест-сертификации
Уровень 1
– Создать пакеты тестового покрытия.
– Установить систему непрерывной сборки.
– Ранжировать тесты на малые, средние и большие.
– Определить недетерминированные тесты.
– Создать набор смоук-тестов.
Уровень 2
– Не выпускать, пока не пройдут все тесты.
– Обязательно выполнять смоук-тесты до отправки кода.
– Инкрементальное покрытие всеми тестами не меньше 50%.
– Инкрементальное покрытие малыми тестами не меньше 10%.
– Хотя бы одна фича покрыта интеграционным тестом.
Уровень 3
– Создавать тесты для всех нетривиальных изменений
– Общее покрытие малыми тестами не меньше 50%.
– Важные новые фичи покрыты интеграционными тестами.
Уровень 4
– Смоук-тесты запускаются автоматически перед отправкой нового кода.
– Смоук-тесты проходят за время меньше 30 минут.
– Нет недетерминированных тестов.
– Общее тестовое покрытие не меньше 40%.
– Тестовое покрытие только малыми тестами не меньше 25%.
– Все важные фичи покрыты интеграционными тестами.
Уровень 5
– Добавить тест к исправлению всех нетривиальных багов.
– Активно использовать доступные средства анализа.
– Общее тестовое покрытие не меньше 60%.
– Тестовое покрытие только малыми тестами не меньше 40%.
Сначала мы обкатали программу на нескольких командах разработчиков, которые и так были позитивно настроены по отношению к тестированию. Они хотели улучшить свои навыки. Когда мы сбалансировали механизм наград, мы объявили открытым большое соревнование за бейджи по всей компании. Наша программа была принята на ура.
Продать нашу идею оказалось не так сложно, как казалось. Команды разработки только выигрывали от этого:
– Они получали поддержку от опытных тестировщиков, которые согласились стать наставникамитест-сертификации. Ресурсы тестирования всегда в дефиците, а присоединившись к программе, команда получала больше тестировщиков, чем ей было положено официально.
– Они получали помощь экспертов и учились лучше писать малые тесты.
– Они видели, какие команды лучше проводят тестирование, и понимали, у кого стоило учиться.
– Они могли похвастаться перед другими командами своим уровнем тест-сертификации.
Вся компания могла следить, как команды разработки зарабатывают уровни.
Руководители разработки успешных команд получали хорошие отзывы от руководителей направления продуктивности разработки. Команды, которые выражали скептицизм и подтрунивали над участвующими, на самом деле рисковали. Если ресурсы тестирования так сложно получить, зачем лишний раз разочаровывать ребят из направления продуктивности? Конечно, не все шло гладко. Дадим слово людям, которые руководили этой программой.