Главная страница
Навигация по странице:

  • Преимущества герметичных SUT

  • ПРИМЕР: РИСКИ ТЕСТИРОВАНИЯ В ПРОДАКШЕНЕ И WEBDRIVER TORSO

  • Ограничение размера SUT

  • Запись/воспроизведение

  • Тестовые данные

  • Типы больших тестов

  • Функциональное тестирование одного или нескольких двоичных файлов

  • Тестирование в браузерах и на мобильных устройствах

  • Тестирование производительности, нагрузочное тестирование и стресс-тесты

  • Делай как вGoogle


    Скачать 5.77 Mb.
    НазваниеДелай как вGoogle
    Дата31.05.2022
    Размер5.77 Mb.
    Формат файлаpdf
    Имя файлаDelay_kak_v_Google_Razrabotka_programmnogo_obespechenia_2021_Tom.pdf
    ТипДокументы
    #559735
    страница37 из 69
    1   ...   33   34   35   36   37   38   39   40   ...   69
    Рис. 14.4. Цепочка тестов
    Структура большого теста
    Большие тесты не связаны ограничениями, которые накладываются на маленькие тесты, и могут включать все что угодно, но они тоже подчиняются некоторым общим

    288
    Глава 14. Крупномасштабное тестирование закономерностям. Обычно большие тесты образуют рабочий процесс со следующими этапами:
    y получение SUT;
    y генерирование необходимых тестовых данных;
    y выполнение операций с SUT;
    y проверка результатов.
    SUT
    Одним из ключевых компонентов больших тестов является вышеупомянутая SUT
    (рис. 14.5). Типичный юнит-тест сосредоточен на одном классе или модуле. Более того, код теста выполняется в том же процессе (или виртуальной машине Java [JVM], если написан на Java), что и тестируемый код. Большие тесты, в свою очередь, вклю- чают один или несколько тестируемых процессов и код теста, который часто (но не всегда) выполняется в своем собственном процессе.
    Клиент пользователя
    SUT
    Сервер пользовательского интерфейса
    Сервер данных
    Сервер данных
    Запрос пользователя
    Ответ системы
    Еще один сервер
    Рис. 14.5. Пример SUT
    Мы в Google используем разные виды SUT, область охвата которых влияет на охват соответствующих больших тестов (чем больше SUT, тем больше тест, проверяющий ее). Каждую разновидность SUT можно оценить по двум основным факторам:
    Герметичность
    Степень изоляции от использования другими компонентами среды (помимо теста) и взаимодействий с ними. SUT с высокой герметичностью меньше под- вержена проблемам, связанным с конкурентным выполнением и нестабильностью инфраструктуры.
    Достоверность
    Степень точности отражения продакшена. SUT с высокой достоверностью будет состоять из двоичных файлов, напоминающих реальные реализации (ис-

    Структура большого теста
    289
    пользующие схожие конфигурации и инфраструктуру и имеющие общую с SUT топологию).
    Часто эти два фактора вступают в прямой конфликт друг с другом.
    Вот несколько примеров SUT:
    SUT с единственным процессом
    Вся SUT упакована в один двоичный файл (даже если реальная версия состоит из нескольких файлов). Код теста может быть упакован в тот же двоичный файл.
    Такая комбинация кода теста и SUT может считаться маленьким тестом, если весь код выполняется в одном потоке, но она недостоверно отражает топологию и конфигурацию продакшена.
    SUT с единственной машиной
    SUT состоит из одного или нескольких двоичных файлов (как и продакшен), а код теста находится в своем двоичном файле. И система, и тест выполняются на одной машине. Этот подход применяется для тестов среднего размера. В этом случае желательно использовать реальную конфигурацию запуска для каждого двоичного файла для более высокой достоверности.
    SUT с несколькими машинами
    SUT распределена по нескольким машинам (подобно развертыванию продакшена в облаке). Этот вариант дает еще более высокую достоверность, чем вариант с един- ственной машиной, но делает тесты большими и, следовательно, нестабильными из-за возможных ошибок в сети.
    Общая среда (промежуточная среда и продакшен)
    Вместо запуска отдельной SUT тест использует общую среду. Этот вариант имеет самую низкую стоимость, потому что часто общая среда уже существу- ет, но тест может конфликтовать с процессами, выполняемыми параллельно, и копирование тестируемого кода в среду требует времени. Тестирование в продакшене также увеличивает риск отрицательного влияния на конечного пользователя.
    Гибриды
    Комбинация предыдущих вариантов: запуск части SUT при ее взаимодействии с общей средой. Обычно тестируемый объект запускается явно, но его серверная часть общедоступна. Такая большая компания, как Google, не может запустить несколько копий всех взаимосвязанных серверов, поэтому нам приходится ис- пользовать гибридизацию в той или иной форме.
    Преимущества герметичных SUT
    SUT может быть основным источником ненадежности и низкой скорости выполне- ния большого теста. Использование продакшена в тесте не требует дополнительных накладных расходов на развертывание SUT, но тестирование не начинается, пока тестируемый код не будет полностью размещен в среде. Поскольку тесты не могут

    290
    Глава 14. Крупномасштабное тестирование блокировать распространение кода в продакшене, само тестирование в этом случае осуществляется слишком поздно.
    Наиболее распространенная альтернатива — создание гигантской общей промежуточ- ной среды для выполнения тестов. Обычно этот подход является частью продвижения новой версии, но, опять же, выполнение теста возможно, только когда тестируемый код находится в среде. Некоторые команды позволяют инженерам «резервировать» временное окно в промежуточной среде для развертывания и тестирования нового кода, но этот вариант не масштабируется с ростом числа инженеров или тестируемых служб, потому что вероятность конфликтов между ними тоже возрастает.
    Другой подход — поддержка облачных или машинно-герметичных SUT. Такие среды позволяют избежать конфликтов и не требуют резервировать время для раз- вертывания кода.
    ПРИМЕР: РИСКИ ТЕСТИРОВАНИЯ В ПРОДАКШЕНЕ
    И WEBDRIVER TORSO
    Выше мы сказали, что тестирование в продакшене — рискованное предприятие. Однажды во время такого тестирования произошел забавный инцидент с Webdriver Torso. Мы ис- кали возможность проверить правильность отображения видео в продакшене YouTube: создали сценарии, автоматически генерирующие тестовые видеоролики, выгрузили их и приступили к проверке качества. Для этого мы использовали общедоступный канал на
    YouTube под названием Webdriver Torso, принадлежащий Google. Но этот канал был обще- доступным, как и большинство видеороликов.
    Впоследствии этот канал был упомянут в статье на Wired (
    https://oreil.ly/1KxVn
    ), что привело к появлению множества публикаций о нем в СМИ и последующим попыткам разгадать его тайну. Наконец, один блогер (
    https://oreil.ly/ko_kV
    ) связал происходящее с Google. Тогда мы рассказали правду, немало повеселившись, в том числе о рикроллинге и «пасхалках», так что все закончилось как нельзя лучше. Этот инцидент заставил нас задуматься о возмож- ности обнаружения конечными пользователями тестовых данных, которые мы добавляем в продакшен, и быть готовыми к этому.
    Ограничение размера SUT
    В тестировании есть опасные границы, за которые иногда не стоит выходить. Тесты, проверяющие не только внешние, но и внутренние интерфейсы, становятся слишком болезненными, потому что, как известно, тесты пользовательского интерфейса не- надежны и дороги по двум причинам:
    y оформление пользовательских интерфейсов часто меняется (без изменений в их базовом поведении), что делает тесты пользовательского интерфейса хрупкими;
    y пользовательские интерфейсы часто имеют асинхронное поведение, которое сложно тестировать.
    Иногда полезно проводить сквозное тестирование пользовательского интерфейса вплоть до внутренней реализации службы, но при этом тесты будут иметь муль-

    Структура большого теста
    291
    типликативную стоимость обслуживания. Поэтому если внутренняя реализация имеет общедоступный API, проще разделить тесты на связанные группы по границе, разделяющей пользовательский интерфейс и API, и использовать общедоступный
    API для сквозного тестирования. Такой подход уместен и для пользовательского веб-интерфейса, и для интерфейса командной строки (CLI, command-line interface), и для обычных или мобильных приложений.
    Еще одна особая граница отделяет сторонние зависимости. Сторонние системы могут не иметь общедоступной среды для тестирования, а в некоторых случаях отправка трафика таким системам обходится слишком дорого. Поэтому мы не рекомендуем использовать настоящий сторонний API в автоматизированных тестах и проводить границу тестирования по сторонним зависимостям.
    Для решения проблем, связанных с размером теста, мы уменьшаем SUT, заменяя ее базы данных базами данных в памяти и исключая один из серверов, находящийся за рамка- ми SUT (рис. 14.6). Такая SUT больше подходит для тестирования на одной машине.
    Клиент пользователя
    SUT
    Сервер пользовательского интерфейса
    Сервер данных
    Сервер данных
    Хранилище данных в памяти
    Хранилище данных в памяти
    Сервер-дублер
    Запрос пользователя
    Ответ системы
    Соединение с сервером-дублером
    Обмен локальными данными между герметичными серверами
    Рис. 14.6. SUT уменьшенного размера
    Ключом к улучшению качества тестирования является компромисс между до- стоверностью и стоимостью/надежностью, а также определение разумных границ.
    Организовав возможность запускать несколько двоичных файлов и тестов на тех же машинах, где выполняются компиляция, компоновка и юнит-тестирование, мы получаем самые простые и стабильные интеграционные тесты.
    Запись/воспроизведение
    В предыдущей главе мы обсудили приемы использования тестовых дублеров и спо- собы отделения тестируемого класса от его сложных для тестирования зависимостей.
    Однако можно создавать дублеры для целых серверов и процессов, используя фик- тивные версии, заглушки или имитации серверов или процессов с эквивалентным
    API. Правда, при этом нет никакой гарантии, что такой тестовый дублер будет со- ответствовать контракту подменяемого реального сервера или процесса.

    292
    Глава 14. Крупномасштабное тестирование
    Тестовый дублер помогает справиться с проблемой зависимостей SUT, но как узнать, действительно ли он отражает реальное поведение зависимости? За пределами Google все большую популярность приобретает фреймворк тестирования контрактов (
    https://
    oreil.ly/RADVJ
    ), в котором клиент определяет шаблон поведения службы, сообщая, что в ответ на некие входные данные он ожидает получить конкретный результат. Затем служба использует эту пару входных/выходных данных в тесте, который показывает, соответствует ли результат ожиданиям. Есть два общедоступных инструмента для тестирования контрактов: Pact Contract Testing (
    https://docs.pact.io
    ) и Spring Cloud
    Contracts (
    https://oreil.ly/szQ4j
    ), но сильная зависимость Google от Protocol Buffers не позволяет использовать их внутри компании.
    В самом популярном в Google подходе (
    https://oreil.ly/-wvYi
    ) (для которого существует общедоступный API) больший тест при запуске генерирует меньший тест, записывая трафик к внешним службам и воспроизводя этот трафик при выполнении меньших тестов. Больший тест, или «Режим записи», выполняется непрерывно после отправки кода в репозиторий, его главная цель — сгенерировать журналы трафика (для этого он должен завершиться успехом). Меньший тест, или «Режим воспроизведения», используется во время разработки и предварительного тестирования.
    Один из интересных аспектов этого механизма записи/воспроизведения заключает- ся в необходимости использования специальных средств, чтобы определить, какой результат послать в ответ на те или иные входные данные. Эти средства похожи на заглушки и фиктивные объекты, которые тоже используют сопоставление аргументов для выбора результирующего поведения.
    Что происходит с новыми тестами или тестами, в которых поведение клиента существенно меняется? В этих случаях вход может перестать соответствовать со- держимому записанного файла трафика, поэтому тест в режиме воспроизведения завершится неудачей и инженер должен будет запустить тест в режиме записи, чтобы сгенерировать новый трафик (важно, чтобы тесты записи были простыми, быстрыми и стабильными).
    Тестовые данные
    Тестам нужны данные, а большим тестам нужны данные двух типов:
    Начальные данные
    Данные, предварительно сгенерированные в SUT и отражающие состояние си- стемы в начале теста.
    Тестовый трафик
    Данные, посылаемые тестом в SUT во время выполнения.
    Сгенерировать начальное состояние больших и изолированных SUT часто на не- сколько порядков сложнее, чем настроить юнит-тест, поскольку начальное состояние включает:

    Структура большого теста
    293
    Конфигурационные данные
    Иногда данные, используемые для настройки среды, хранятся в специальных таблицах базы данных. Действительные двоичные файлы службы обращаются к базе данных и могут не запуститься, если искомые данные в ней отсутствуют.
    Реалистичные базовые данные
    Чтобы приблизить SUT к реальности, может потребоваться реалистичный по количеству и качеству набор базовых данных. Например, большому тесту социальной сети наверняка понадобится реалистичный социальный граф, в котором должно быть достаточно много тестовых пользователей с реалистич- ными профилями, а также достаточное количество взаимосвязей между этими пользователями.
    API для заполнения
    API для передачи начальных данных могут быть очень сложными. Иногда воз- можна прямая запись данных в хранилище, но она не задействует триггеры и проверки, выполняемые фактическими двоичными файлами во время записи.
    Данные могут быть сгенерированы разными способами:
    Вручную
    Для подготовки данных для нескольких служб в большой SUT вручную может потребоваться много усилий.
    Копирование
    Данные можно скопировать из продакшена. Например, при тестировании гео- графической карты проверка изменений проводится на скопированных данных из продакшена.
    Выборка
    При копировании можно получить слишком много данных. Выборка данных по- может уменьшить их объем и тем самым сократить время тестирования и облег- чить анализ результатов. «Интеллектуальная выборка» — это метод копирования минимального количества данных, необходимых для достижения максимального охвата.
    Проверка
    После запуска SUT и передачи трафика мы должны проверить ее поведение. Сделать это можно несколькими способами:
    Вручную
    Так же как при тестировании локального двоичного файла, проверка вручную предполагает взаимодействие человека с SUT. Это может быть регрессионное тестирование в соответствии с согласованным планом или исследовательское тестирование, при котором проверяются различные сценарии взаимодействия для выявления возможных новых сбоев.

    294
    Глава 14. Крупномасштабное тестирование
    Обратите внимание, что регрессионное тестирование вручную не масштабируется линейно: чем больше размер системы и чем больше сценариев взаимодействия с ней, тем больше человеко-часов необходимо затратить на ее тестирование вручную.
    С использованием утверждений
    Так же как в юнит-тестах, утверждения в более крупных тестах явно прове- ряют предполагаемое поведение системы. Например, в интеграционном тесте для Google Search, проверяющем поиск по строке xyzzy
    , утверждение может выглядеть так:
    assertThat(response.Contains("Colossal Cave"))
    A/B-сравнение (отличий)
    A/B-тестирование предполагает запуск двух копий SUT, отправку им одних и тех же данных и сравнение полученных результатов. Предполагаемое поведение в этом случае не определяется явно: инженер должен вручную отыскать все отличия и убедиться, что они ожидаемы.
    Типы больших тестов
    Теперь объединим разные подходы к организации SUT, сбору данных и проверкам и перейдем к созданию больших тестов. Каждый тест имеет разные особенности, касающиеся смягчаемых рисков, трудозарат на разработку, отладку и сопровождение, а также ресурсов, необходимых для запуска.
    Ниже мы привели список разных видов больших тестов, используемых в Google, и описали, как они конструируются, для чего служат и какие ограничения имеют:
    y функциональное тестирование одного или нескольких двоичных файлов;
    y тестирование в браузерах и на мобильных устройствах;
    y тестирование производительности, нагрузочное тестирование и стресс-тесты;
    y тестирование конфигурации развертывания;
    y исследовательское тестирование;
    y
    A/B-тестирование различий (регрессионное);
    y приемочное тестирование пользователем (UAT, user acceptance testing);
    y зонды и канареечный анализ;
    y восстановление после аварии и проектирование хаоса;
    y оценка действий пользователей.
    Как управлять таким большим количеством комбинаций и, соответственно, широким спектром тестов? Частью проектирования ПО является составление плана тести- рования, а ключевой частью плана тестирования является стратегическое описание необходимых видов тестирования и тестов каждого вида. Это описание определяет основные риски и подходы, помогающие их смягчить.

    Типы больших тестов
    295
    В Google есть специальная должность «инженер-тестировщик», и один из навыков хорошего инженера-тестировщика — умение наметить стратегию тестирования на- ших продуктов.
    Функциональное тестирование одного или нескольких двоичных файлов
    Тесты этого типа имеют следующие характеристики:
    y
    SUT: единственная герметичная машина или изолированное развертывание в облаке;
    y данные: подготавливаются вручную;
    y проверка: с использованием утверждений.
    Как мы уже видели, юнит-тесты не способны протестировать сложную систему с полной достоверностью, потому что они оформлены совсем не так, как настоящий код. Многие сценарии функционального тестирования взаимодействуют с двоич- ным файлом иначе, чем юнит-тесты с классами внутри этого двоичного файла. Для функционального тестирования необходимы отдельные SUT, и по этой причине они считаются каноническими большими тестами.
    Тестирование взаимодействий между несколькими двоичными файлами сложнее, чем тестирование одного двоичного файла. В среде с микросервисами, в которой службы развертываются в виде нескольких двоичных файлов, функциональный тест может охватывать реальные взаимодействия между двоичными файлами, обращаясь к SUT, состоящей из всех необходимых двоичных файлов, и взаимодействуя с ней через общедоступный API.
    Тестирование в браузерах и на мобильных устройствах
    Тестирование веб-интерфейсов и мобильных приложений — это вид функциональ- ного тестирования одного или нескольких взаимодействующих двоичных файлов.
    Для конечных пользователей общедоступным API является само приложение.
    Чтобы обеспечить больший охват тестирования, недостаточно выполнить юнит- тести рование кода реализации и нужно использовать тесты, взаимодействующие с приложением через его внешний интерфейс.
    Тестирование производительности, нагрузочное тестирование и стресс-тесты
    Тесты этого типа имеют следующие характеристики:
    y
    SUT: изолированное развертывание в облаке;
    y данные: подготавливаются вручную или импортируются из продакшена;
    y проверка: различий (параметров производительности).
    Иногда можно провести тестирование производительности, нагрузочное тестирова- ние и стресс-тестирование небольшого модуля, но чаще такое тестирование требует одновременной отправки трафика внешнему API. Это означает, что тесты этого типа

    1   ...   33   34   35   36   37   38   39   40   ...   69


    написать администратору сайта