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

  • Распространенные недостатки юнит-тестов

  • Проблемы в конфигурациях

  • Проблемы, возникающие под нагрузкой

  • Неожиданное поведение, входные данные и побочные эффекты

  • Непредсказуемое поведение и «эффект вакуума»

  • Недостатки больших тестов

  • Большие тесты в масштабе Google

  • Рис. 14.3.

  • Делай как вGoogle


    Скачать 5.77 Mb.
    НазваниеДелай как вGoogle
    Дата31.05.2022
    Размер5.77 Mb.
    Формат файлаpdf
    Имя файлаDelay_kak_v_Google_Razrabotka_programmnogo_obespechenia_2021_Tom.pdf
    ТипДокументы
    #559735
    страница36 из 69
    1   ...   32   33   34   35   36   37   38   39   ...   69
    Достоверность
    Основная цель создания больших тестов — увеличение достоверности тестиро- вания. Достоверность — это свойство теста, позволяющее ему отражать реальное поведение SUT.
    Рассуждая о достоверности, полезно рассмотреть возможные градации тестовых сред. Как показано на рис. 14.1, юнит-тесты объединяют тестовый код и небольшую часть тестируемого кода в отдельную единицу (юнит), которая позволяет проверить код, но этот код выполняется в тесте совсем не так, как в продакшене. Продакшен, естественно, является самой точной средой для тестирования. Существует также целый спектр промежуточных вариантов сред для тестирования. Ключом к круп- номасштабному тестированию является правильное определение его масштаба, потому что увеличение достоверности сопровождается ростом затрат и (в случае продакшена) увеличением риска отказа.
    Достоверность среды
    Среда отладки
    Изолированная среда
    Юнит
    Один процесс
    Продакшен
    Рис. 14.1. Увеличение достоверности с ростом масштаба тестирования
    Достоверность тестов также может измеряться по соответствию их содержимого действительности. Многие большие тесты, написанные вручную, отклоняются инже- нерами, если данные в них выглядят нереалистичными. Тестовые данные, скопиро- ванные из продакшена, намного ближе к реальности, но их применение подразумевает генерирование реалистичного тестового трафика перед запуском нового кода. Это особенно сложно сделать для искусственного интеллекта, в котором «начальные» данные часто страдают внутренней смещенностью. И поскольку большинство данных

    Что такое большие тесты?
    281
    для юнит-тестов готовится вручную, эти тесты охватывают лишь узкий диапазон возможных сценариев и имеют тенденцию соответствовать предубеждениям автора.
    Неохваченные сценарии из-за пропущенных данных снижают достоверность тестов.
    Распространенные недостатки юнит-тестов
    Большие тесты могут понадобиться там, где маленькие тесты не справляются со своими задачами. В следующих подразделах мы перечислим некоторые области, в которых юнит-тесты не обеспечивают достаточного для снижения рисков охвата тестирования.
    Неточные дублеры
    Один юнит-тест обычно охватывает один класс или модуль. Для избавления от тяжеловесных или труднотестируемых зависимостей в таких тестах часто исполь- зуются тестовые дублеры (глава 13). Но когда зависимости заменяются дублерами, возникает вероятность появления несоответствий между поведением реальной реализации и поведением дублеров.
    Почти все юнит-тесты в Google пишутся теми же инженерами, которые пишут тести- руемый код. Когда возникает необходимость использовать дублеры в юнит-тестах и когда в роли этих дублеров выступают фиктивные объекты, инженер, пишущий юнит-тест, определяет фиктивный объект и его предполагаемое поведение. Но, как правило, этот инженер не пишет зависимости, для которых создает фиктивные объ- екты, и может неправильно понимать поведение реальных реализаций. Сходство между зависимостью и ее представлением в тесте дублер определяет поведенческим контрактом, и если инженер ошибается в определении фактического поведения за- висимости, контракт становится недействительным.
    Кроме того, фиктивные объекты могут устаревать. Если юнит-тест, основанный на использовании фиктивного объекта, не виден автору реальной реализации, то, ког- да реальная реализация изменится, автор теста не получит сигнала о том, что тест
    (и проверяемый код) следует обновить, чтобы учесть изменения.
    Обратите внимание, что если команды создают фиктивные объекты для имитации своих собственных служб, то эта проблема обычно не возникает (глава 13).
    Проблемы в конфигурациях
    Юнит-тесты охватывают содержимое двоичного файла, который, как правило, не является полностью автономным с точки зрения выполнения. Обычно для двоичного файла определяется какая-то конфигурация развертывания или запускающий сцена- рий. При этом реальные экземпляры двоичных файлов, обслуживающие конечных пользователей, имеют свои файлы или базы данных с конфигурациями.
    Если в этих файлах возникнут какие-то проблемы или состояние, определяемое ими, потеряет совместимость с состоянием, указанным в двоичном файле теста, по- страдают конечные пользователи. Юнит-тесты не смогут самостоятельно выявить

    282
    Глава 14. Крупномасштабное тестирование такую несовместимость
    1
    . Всегда храните конфигурацию в VCS вместе с кодом, чтобы при отказе изменения в конфигурации можно было идентифицировать как источник ошибки и встроить их проверку в большие тесты.
    В Google изменения в конфигурации часто оказываются главной причиной масштаб- ных сбоев. Мы поняли это не сразу и допустили ряд ошибок. Например, в 2013 году случился глобальный перерыв в работе Google из-за сбоя в конфигурации сети, которая никогда не проверялась. Конфигурации обычно пишутся на языках кон- фигурации, а не на языках программирования, на которых пишется код. Также они часто имеют более короткий цикл передачи в эксплуатацию, чем двоичные файлы, и их сложнее проверить. Все это увеличивает вероятность отказа. Но в этом случае
    (и в других) конфигурация хранилась в VCS, и мы смогли быстро выявить причину сбоя и устранить проблему.
    Проблемы, возникающие под нагрузкой
    Мы в Google требуем, чтобы юнит-тесты были маленькими и быстрыми, вписыва- лись в нашу стандартную инфраструктуру тестирования, а также многократно за- пускались в ходе рабочего процесса инженера. Но для оценки производительности, устойчивости к нагрузкам и стресс-тестирования часто требуется отправлять большие объемы трафика в двоичный файл. Эти объемы трудно смоделировать в типовой модели юнит-тестирования. А в нашем понимании большие объемы — это тысячи и даже миллионы запросов в секунду (в рекламе и торгах в режиме реального вре- мени (
    https://oreil.ly/brV5-
    ))!
    Неожиданное поведение, входные данные и побочные эффекты
    Юнит-тесты ограничены воображением инженера, пишущего их. То есть они могут проверять только ожидаемое поведение и входные данные. Однако пользователи чаще сталкиваются с непредвиденными проблемами (если бы это было не так, про- блемы едва ли достигли бы конечных пользователей). Это говорит о том, что для проверки непредвиденного поведения необходимы разные методы тестирования.
    Здесь важную роль играет закон Хайрама (
    http://hyrumslaw.com
    ): даже если мы мог- ли бы установить строгое соответствие системы указанному контракту, любое на- блюдаемое поведение системы будет зависеть не только от заявленного контракта, но и от действий пользователя. Маловероятно, что одни лишь юнит-тесты смогут протестировать все наблюдаемые поведения, не указанные в общедоступном API.
    Непредсказуемое поведение и «эффект вакуума»
    Юнит-тесты имеют ограниченную область охвата (особенно при широком использо- вании тестовых дублеров) и не могут обнаружить изменение поведения, возникающее за ее пределами. А поскольку юнит-тесты предназначены для быстрой и надежной работы, они намеренно не включают хаос реальных зависимостей, сети и данных.
    1
    Подробнее об этом в разделе «Непрерывная поставка» (глава 23) и в главе 25.

    Что такое большие тесты?
    283
    Юнит-тест подобен явлению из теоретической физики: он находится в вакууме, на- дежно изолирован от хаоса реального мира, имеет высокие скорость и надежность, но игнорирует некоторые категории дефектов.
    Недостатки больших тестов
    В предыдущих главах мы обсудили основные свойства удобного для разработчика теста. В частности, тест должен обладать следующими характеристиками:
    Надежность
    Он должен быть стабильным и обеспечивать полезный сигнал успех/неудача.
    Высокая скорость
    Он должен быть достаточно быстрым, чтобы не прерывать рабочий процесс инженера.
    Масштабируемость
    Все тесты должны эффективно выполняться как перед отправкой кода в репо- зиторий, так и в будущем.
    Хорошие юнит-тесты обладают всеми этими свойствами. Большие тесты, напротив, часто не соответствуют этим требованиям. Они не всегда надежны, потому что по- лагаются на инфраструктуру, выполняются медленно и плохо масштабируются из-за более высоких требований к ресурсам и времени, а также они слабо изолированы и могут конфликтовать друг с другом.
    Кроме того, большие тесты имеют две другие проблемы. Первая проблема связана с владением. Юнит-тестом явно владеет инженер (и команда), которому принадле- жит модуль. Большой тест охватывает несколько модулей и, следовательно, может иметь нескольких владельцев. В долгосрочной перспективе неясно, кто отвечает за поддержку такого теста и диагностику проблем, когда тест терпит неудачу. Без четкого владения тест приходит в негодность.
    Вторая проблема больших тестов — это стандартизация (или ее отсутствие). В от- личие от юнит-тестов, большие тесты страдают отсутствием стандартизации с точки зрения инфраструктуры и процессов, с помощью которых они пишутся, выполняются и отлаживаются. Реализация больших тестов зависит от архитектурных решений, из-за чего возникают различия в типах требуемых тестов. Например, регрессионные
    A/B-тесты для Google Ads пишутся и выполняются совершенно иначе, чем аналогич- ные тесты для Google Search, и иначе, чем для Google Drive. Они используют разные платформы, разные языки, разные инфраструктуры, разные библиотеки и разные фреймворки тестирования.
    Поскольку большие тесты могут выполняться множеством разных способов, их не- редко игнорируют во время крупномасштабных изменений (глава 22). В инфраструк- туре отсутствует стандартный способ выполнения больших тестов, и невозможно требовать от людей, выполняющих крупномасштабные изменения, знания особен-

    284
    Глава 14. Крупномасштабное тестирование ностей тестирования в каждой команде. Тесты, проверяющие интеграцию команд, могут потребовать объединения несовместимых инфраструктур. Из-за отсутствия стандартизации мы также не можем обучить сотрудников какому-то единому под- ходу к тестированию, что вызывает в инженерах устойчивое непонимание мотивов разработки больших тестов.
    Большие тесты Google
    Обсуждая историю развития тестирования в Google (глава 11), мы упоминали, как проект GWS внедрил автоматизированное тестирование в 2003 году и как это стало переломным моментом. Однако и до этого момента у нас имелись автоматизирован- ные тесты, но в основном только большие и огромные. Например, в AdWords сквозной тест был создан еще в 2001 году. Он использовался для проверки разных сценариев использования продукта. В 2002 году в Google Search был написан аналогичный
    «регрессионный тест» для кода индексации, а в проекте AdSense (который еще даже не был официально запущен) был создан свой вариант теста AdWords.
    Также примерно в 2002 году появились другие модели «больших» тестов. Пользо- вательский интерфейс Google Search в значительной степени опирался на ручную проверку качества — версии сквозных тестов, которые выполнялись вручную.
    В Gmail создали свою версию «локальной демонстрационной» среды для локального тестирования вручную — сценарий, генерирующий локальную среду для сквозного тестирования Gmail, включающую несколько сгенерированных тестовых учетных записей с почтовыми данными.
    Когда был выпущен C/J Build — наш первый фреймворк непрерывной сборки, он не проводил различий между юнит-тестами и другими тестами до того, как произошли два важных события. Во-первых, компания Google сосредоточилась на юнит-тестах, чтобы сформировать правильную пирамиду тестирования и добиться распростране- ния юнит-тестирования. Во-вторых, TAP, заменившая C/J Build в роли нашей офици- альной системы непрерывной сборки, поддерживала тесты только с определенными характеристиками: герметичные, создаваемые отдельно для каждого изменения и выполняемые в кластере сборки/тестирования в течение ограниченного времени.
    Требованиям TAP удовлетворяли большинство юнит-тестов, но это не устранило потребность в других видах тестирования, и большие тесты продолжали заполнять пробелы в охвате. Мы поддерживали C/J Build еще несколько лет специально для больших тестов, пока не появились более новые системы.
    Большие тесты и время
    На протяжении всей книги мы рассматриваем влияние времени на программную инженерию, потому что Google продолжает создавать ПО более 20 лет. Как большие тесты зависят от измерения времени? Мы знаем, что с увеличением ожидаемого срока службы кода некоторые действия приобретают особую значимость, а тестирование

    Большие тесты Google
    285
    должно сопровождать развитие систем всегда, но виды тестов меняются в течение этого срока.
    Как отмечалось выше, юнит-тесты приобретают смысл, если ожидаемая продол- жительность жизни ПО превышает несколько часов. Для кода со сроком жизни, измеряемом минутами (некоторые небольшие сценарии), более распространено ручное тестирование, и в роли SUT, которая обычно выполняется локально, высту-
    пает продакшен (особенно в одноразовом сценарии). Для кода с более длительным сроком жизни тоже используется ручное тестирование, но оно применяется к дру- гой системе, отличной от продакшена, потому что часто реальный экземпляр такой системы размещается в облаке.
    Остальные большие тесты полезны для более долгоживущего ПО и требуют слож- ного сопровождения.
    Кстати, зависимость вида тестирования от срока действия тестируемого кода яв- ляется одной из причин появления антипаттерна «рожок мороженого» (глава 11):
    Тесты,
    выполняемые вручную
    Автоматизированные тесты пользовательского интерфейса
    Интеграционные тесты
    Юнит- тесты
    Рис. 14.2. Антипаттерн тестирования «рожок мороженого»
    Когда в начале разработки используется в основном ручное тестирование (если ин- женеры считают, что код просуществует всего несколько минут), ручные тесты накап- ливаются и начинают доминировать в наборе тестов. Например, часто используется такой подход: изменить сценарий (или приложение), запустить и протестировать его, а затем добавлять в него новые возможности и тестировать их вручную. Созданный таким способом прототип позднее становится работоспособным и начинает исполь- зоваться другими инженерами, но для него нет автоматических тестов.
    Хуже того, если код с трудом поддается юнит-тестированию (в первую очередь из-за особенностей его реализации) и единственные автоматизированные тесты, которые

    286
    Глава 14. Крупномасштабное тестирование можно для него написать, являются сквозными, это значит, что мы случайно создали
    «устаревший код».
    В долгосрочной перспективе очень важно получить правильную пирамиду тестиро- вания: написать юнит-тесты в первые дни разработки, а затем добавить автомати- ческие интеграционные тесты и полностью избавиться от сквозного тестирования вручную. Мы смогли сделать обязательным юнит-тестирование перед отправкой кода в репозиторий, но в долгосрочной перспективе также важно заполнить пробел между юнит-тестами и ручными тестами.
    Большие тесты в масштабе Google
    Казалось бы, большие тесты больше соответствуют крупномасштабному ПО, но даже если это так, сложность создания, запуска, сопровождения и отладки этих тестов растет быстрее, чем сложность юнит-тестов.
    В системе, состоящей из микросервисов или отдельных серверов, схема взаимодей- ствий имеет вид графа, число узлов которого равно N. Каждый раз, когда в этот граф добавляется новый узел, число различных путей через него растет в геометрической прогрессии.
    На рис. 14.3 изображена воображаемая SUT. Она включает социальную сеть с поль- зователями (граф), через которую распространяются поток сообщений и рекламные объявления от рекламодателей, пересылаемые в потоке сообщений. Эта система включает две группы пользователей, два пользовательских интерфейса, три базы данных, конвейер индексирования и шесть серверов. В графе есть 14 ребер. Тести- рование всех возможных путей в этом графе уже затруднено, а что произойдет, если добавить в него дополнительные услуги, конвейеры и базы данных: фотографии и изображения, анализ фотографий с помощью машинного обучения и т. д.?
    Количество различных сценариев для сквозного тестирования может возрастать экспоненциально или даже комбинаторно в зависимости от структуры SUT, и этот рост не масштабируется. Как следствие, с ростом системы мы вынуждены искать альтернативные, более масштабируемые политики тестирования, чтобы сохранить управляемость.
    Ценность масштабируемых тестов тоже увеличивается с ростом системы из-за потребности в достоверности: по мере движения к более глубоким слоям, когда службы тестируются с использованием дублеров, имеющих более низкую досто- верность (1 –
    ε), вероятность появления ошибок при их объединении изменяется экспоненциально в зависимости от глубины слоя N. Вернемся к примеру SUT: если заменить серверы пользователей и рекламы дублерами с менее высокой до- стоверностью (например, с 10%-ной неточностью), вероятность ошибки составит
    99 % (то есть 1 – (0,1 × 0,1)). И это только с двумя менее достоверными дублерами.
    Соответственно, важно реализовать большие тесты так, чтобы они хорошо работали в интересующем масштабе, но поддерживали достаточно высокую достоверность.

    Структура большого теста
    287
    Пользователь
    Пользовательский интерфейс потока сообщений
    Сервер рекламы
    Конвейер рекламы
    Сервер пользователей
    Сервер потока сообщений
    Пользовательский интерфейс рекламодателей
    База данных рекламы
    База данных пользователей
    Сервер оциального графа
    База данных социального графа
    Рекламодатель
    Рис. 14.3. Пример чрезвычайно маленькой SUT: социальная сеть с поддержкой рекламы
    СОВЕТ: «МИНИМАЛЬНО ВОЗМОЖНЫЙ ТЕСТ»
    Даже в интеграционном тестировании чем меньше тест, тем лучше, — несколько больших тестов лучше одного огромного. И поскольку область охвата теста часто зависит от области охвата SUT, поиск способов уменьшения SUT помогает сделать тесты меньше.
    Один из способов достичь уменьшения тестов, например в тестировании пользователь- ского пути, который может потребовать участия многих внутренних систем, состоит в том, чтобы связать тесты в «цепочку», представляющую общий сценарий (рис. 14.4). Для этого выходные данные одного теста нужно передать на вход другого путем сохранения этих данных в хранилище.
    Тест
    Тест
    Тест
    Хранилище
    Хранилище
    1   ...   32   33   34   35   36   37   38   39   ...   69


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