— Команда хочет прогонять все тесты для своего проекта после отправки списка изменений в систему управления версиями.
— Команда хочет еженедельно собирать статистику о покрытии кода и отслеживать его прогресс со временем.
Может быть и так, что все вышеперечисленные задания отправляются в систему выполнения тестов Google одновременно. Некоторые из тестов могут захватывать ресурсы, занимая общие машины на целые часы. Другим будет достаточно миллисекунд для выполнения, и они могут благополучно исполняться на одной машине с сотнями других тестов. Когда тесты помечены как малые, средние и большие, гораздо проще планировать расписание выполнения запусков, так как планировщик понимает, сколько времени может занять запуск, и оптимизирует очередь.
Система выполнения тестов Google отличает быстрые задания от медленных по информации о размере тестов. У каждого размера есть верхняя граница времени выполнения теста (табл. 2.1). Размер определяет и потенциальную потребность в ресурсах (табл. 2.2). Система прерывает выполнение и сообщает об ошибке, если тест превышает выделенное для его категории время или доступный объем ресурса. Это мотивирует разработчиков в тестировании назначать правильные метки размеров тестов. Точное определение размеров тестов позволяет системе строить эффективное расписание.
Малые тесты
Средние тесты
Большие тесты
Огромные тесты
Временные цели
(на метод)
Исполнение меньше чем за 100 мс
Исполнение меньше чем за 1 с
Как можно быстрее
Как можно быстрее
Ограничения по времени
Прерывание малых тестов после 1 минуты
Прерывание средних тестов после 5 минут
Прерывание больших тестов после 15 минут
Прерывание огромных тестов после 1 часа
Таблица 2.1. Цели и ограничения времени отработки тестов по их размеру
Ресурс
Большие
Средние
Малые
Сетевые службы (открытие сокета)
Да
Только localhost
Подставные объекты
База данных
Да
Да
Подставные объекты
Доступ к файловой системе
Да
Да
Подставные объекты
Доступ к пользовательским системам
Да
Нежелательно
Подставные объекты
Вызов системных функций
Да
Нежелательно
Подставные объекты
Многопоточность
Да
Да
Нежелательно
Команды приостановки
Да
Да
Нет
Свойства системы
Да
Да
Нет
Таблица 2.2. Использование ресурсов в зависимости от размеров теста
Преимущества разных размеров тестов
Размер теста имеет значение. Он влияет на специфические преимущества теста. На рис. 2.5 показана общая сводка, а ниже мы приводим более подробный список достоинств и недостатков каждого типа тестов.
Рис. 2.5. Ограничения разных размеров тестов
Большие тесты
Достоинства и недостатки больших тестов:
— Большие тесты проверяют самое важное — работу приложения. Они учитывают поведение внешних подсистем.
— Большие тесты могут быть недетерминированными (результат может быть получен разными путями), потому что зависят от внешних подсистем.
— Большой охват усложняет поиск причин при неудачном прохождении теста.
— Подготовка данных для тестовых сценариев может занимать много времени.
— Из-за высокоуровневости больших тестов в них трудно прорабатывать граничные значения. Для этого нужны малые тесты.
Средние тесты
Достоинства и недостатки средних тестов:
— Требования к подставным объектам мягче, а временные ограничения свободнее, чем у малых тестов. Разработчики используют их как промежуточную ступень для перехода от больших тестов к малым.
— Средние тесты выполняются относительно быстро, поэтому разработчики могут запускать их часто.
— Средние тесты выполняются в стандартной среде разработки, поэтому их очень легко запускать.
— Средние тесты учитывают поведение внешних подсистем.
— Средние тесты могут быть недетерминированными, потому что зависят от внешних подсистем.
— Средние тесты выполняются не так быстро, как малые.
Малые тесты
Достоинства и недостатки малых тестов:
— Малые тесты помогают повысить чистоту кода, потому что работают узконаправленно с небольшими методами. Соблюдение требований подставных объектов приводит к хорошо структурированным интерфейсам между подсистемами.
— Из-за скорости выполнения малые тесты выявляют баги очень рано и дают немедленную обратную связь при внесении изменений в код.
— Малые тесты надежно выполняются во всех средах.
— Малые тесты обладают большей детализацией, а это упрощает тестирование граничных случаев и поиск состояний, приводящих к ошибкам, например null-указатели.
— Узкая направленность малых тестов сильно упрощает локализацию ошибок.
— Малые тесты не проверяют интеграцию между модулями — для этого используются другие тесты.
— Иногда сложно применить подставные объекты для подсистем.
— Подставные объекты и псевдосреды могут отличаться от реальности.
Малые тесты способствуют созданию качественного кода, хорошей проработке исключений и получению информации об ошибках. Более масштабные тесты ориентированы на общее качество продукта и проверку данных. Ни один тип тестов не покрывает все потребности продукта в тестировании. Поэтому в проектах Google мы стараемся использовать разумное сочетание всех типов тестов в каждом тестовом наборе. Автоматизация, основанная только на больших комплексных тестах, так же вредна, как и создание только малых юнит-тестов.
На заметку
Малые тесты направлены на проверку качества кода, а средние и большие — на проверку качества всего продукта.
Покрытие кода — отличный инструмент, чтобы оценить, насколько разумно используется сочетание разных размеров тестов в проекте. Проект генерирует один отчет с данными покрытия только для малых тестов, а потом другой отчет с данными только для средних и больших тестов. Каждый отчет в отдельности должен показывать приемлемую величину покрытия для проекта. Если средние и большие тесты в отдельности обеспечивают только 20-процентное покрытие, а покрытие малыми тестами приближается к 100, то у проекта не будет доказательств работоспособности всей системы. А если поменять эти числа местами, скорее всего, расширение или сопровождение проекта потребует серьезных затрат на отладку. Чтобы генерировать и просматривать данные о покрытии кода на ходу, мы используем те же инструменты, которые собирают и выполняют тесты. Достаточно поставить дополнительный флаг в командной строке. Данные о покрытии кода хранятся в облаке, и любой инженер может просмотреть их через веб в любой момент.
Google разрабатывает самые разные проекты, их потребности в тестировании сильно отличаются. В начале работы мы обычно используем правило 70/20/10: 70% малых тестов, 20% — средних и 10% — больших. В пользовательских проектах со сложными интерфейсами или высокой степенью интеграции доля средних и крупных тестов должна быть выше. В инфраструктурных проектах или проектах, где много обработки данных (например, индексирование или обход веб-контента), малых тестов нужно намного больше, чем больших и средних.
Для наблюдения за покрытием кода в Google используется внутренний инструмент — Harvester. Это инструмент визуализации, который отслеживает все списки изменений проекта и графически отображает важные показатели: отношение объема кода тестов к объему нового кода в конкретных списках изменений; размер изменений; зависимость частоты изменений от времени и даты; распределение изменений по разработчикам и т.д. Цель Harvester — дать общую сводку об изменениях в процессе тестирования проекта со временем.
Требования к выполнению тестов
У системы выполнения тестов в Google одинаковые требования ко всем тестам.
— Каждый тест должен быть независим от других, чтобы тесты могли выполняться в любом порядке.
— Тесты не должны иметь долгосрочных последствий. После их завершения среда должна возвращаться в то же состояние, в котором она находилась при запуске.
Требования простые и понятные, но выполнить их оказывается не так просто. Даже если сам тест отвечает требованиям, тестируемая программа может их нарушать, сохраняя файлы данных или изменяя конфигурацию. К счастью, сама среда выполнения тестов Google упрощает соблюдение этих требований.
Что касается требования независимости, инженер во время прогона может установить флаг выполнения тестов в случайном порядке. Эта фича помогает выявить зависимости, связанные с порядком выполнения. Впрочем, случайный порядок может означать, что тесты запускаются параллельно. Система может отправить выполнять два теста на одной машине. Если каждый тест требует единоличного доступа к ресурсам системы, один из них упадет. Например: