Тесты помогают нам обрести уверенность в кодировании, но они также могут и замедлить нас. Как нам найти правильный баланс между их запуском достаточно часто, чтобы выявить проблемы, но не мешать прогрессу?

Даже относительно простые бессерверные приложения могут быть сложными. Со сложностью возникает риск того, что система не будет вести себя так, как предполагалось. У нас есть два оружия против этого риска:

  • Тестирование — попытка выявить как можно больше проблем до выпуска программного обеспечения.
  • Наблюдение — обеспечение того, чтобы когда выпускались проблемы (ни одно программное обеспечение не бывает безупречным!), мы могли обнаружить и диагностировать проблемы как можно скорее.

Обе эти парадигмы связаны с обучением. В частности, изучение того, как система ведет себя.

Наблюдаемость завораживает и имеет первостепенное значение, но для меня основное внимание здесь уделяется тестированию. Тестирование — это средство достижения уверенности в системе, перед ее выпуском.

Завоевание доверия, быстро

Всякий раз, когда мы создаем программное обеспечение, нам нужно принимать решения о том, как мы обеспечим его работу, как задумано. Это означает написание тестов, имитирующих реальные сценарии, а затем запуск этих тестов в нужный момент жизненного цикла доставки, чтобы они выявляли проблемы.

Так что же такого в тестировании, что может дать нам уверенность в том, что наш релиз пройдет хорошо?

  • Много тестов?
  • Много покрытия кода?
  • Тестирование взаимодействия между частями системы?

Может быть. Все это потенциальные индикаторы хорошего тестирования, но за них приходится платить: время. Они стоят времени двумя способами:

  • Время выполнения: для выполнения тестов требуется время, в зависимости от типа тестов они могут выполняться длительно.
  • Время обслуживания: наличие большого количества тестов означает, что необходимо поддерживать большое количество тестов.

Ну и что, если на это потребуется время?

Проблема в том, что если тесты выполняются слишком долго, то произойдет одно из двух: вы будете часто пропускать эти тесты или вы не сможете выпускать их так часто, как должны, потому что ждете, пока ваши тесты закончатся.

Если пропускать тесты, то какой смысл в них? Если вы не пропустите тесты, вы потеряете время, ожидая их завершения, и ваша доставка замедлится.

Причина, по которой это очень важно, заключается в том, что плохое/медленное тестирование напрямую влияет на ВСЕ 4 показателя DORA. И причина, по которой показатели DORA так важны, заключается в том, что зрелость DevOps напрямую коррелирует с успехом в бизнесе. Другими словами, если вы побеждаете DevOps, у вас гораздо больше шансов добиться успеха как у компании.

Вот четыре показателя DORA:

  • Время выполнения: время от первой фиксации до развертывания в рабочей среде — длительные тесты могут увеличить этот показатель.
  • Частота развертывания: как часто происходит развертывание в рабочей среде — длительные тесты ограничивают возможность частого развертывания и, вероятно, приведут к увеличению количества пакетов и снижению частоты развертывания.
  • Время восстановления: сколько времени требуется для восстановления службы с момента возникновения проблемы — чем дольше выполняются тесты, тем больше времени требуется для восстановления идентифицированное исправлениевисправленное развернутое
  • Изменить процент сбоев: какой процент развертываний в рабочей среде приводит к возникновению проблемы, влияющей на обслуживание — тесты, которые не подтверждают правильное поведение (или не выполняются!) в других выпусках, которые будут запущены в производство

Тестирование является неотъемлемой частью успеха в бизнесе, и это подводит меня к ключевому моменту этого поста:

Ценное тестирование — это баланс между хорошо продуманными, полезными тестовыми сценариями и быстрым выполнением для достижения коротких циклов с высоким уровнем достоверности для каждого выпуска.

Это баланс. Золотая середина баланса зависит от нескольких вещей:

  • Насколько мы можем быть терпимы к неудачам?
  • Как быстро мы должны реагировать на изменения?

Насколько мы можем быть терпимы к неудачам?

Такая система, как Умные автомагистрали просто не терпит сбоев, это может привести к серьезным травмам людей. Из-за большого воздействия любых потенциальных проблем имеет смысл потратить больше времени на проверку правильности работы системы, прежде чем изменение будет выпущено.

Однако многие системы оказывают гораздо меньшее влияние в случае сбоя, поэтому тратить большое количество времени на их тестирование перед выпуском — менее ценное использование времени.

Как быстро мы должны реагировать на изменения?

В некоторых обстоятельствах система может оставаться довольно статической в ​​течение длительного времени. В этих случаях допустимо, что изменения занимают много времени, поэтому тестирование, в свою очередь, может занять больше времени.

Однако на конкурентном рынке способность быстро адаптироваться к изменяющимся условиям может стать разницей между успехом и неудачей. В этих случаях для компании может быть большим риском потратить слишком много времени на тестирование, чем негативное влияние пользователей, столкнувшихся с проблемами.

Понимание баланса

Каким бы ни был для вас «правильный баланс», важно понимать, что тестировать, когда и насколько подробно. Начнем с определения некоторых этапов тестирования:

  • Модульные тесты
  • Тест системной интеграции(включая контрактные тесты)
  • Сквозные тесты

На мой взгляд, это основные типы и этапы тестирования (я намеренно игнорирую тестирование нефункциональных требований, например, тестирование производительности, поскольку оно менее универсально для всех вариантов использования).

Когда вы работаете по списку, происходят следующие вещи:

  • Написание и сопровождение тестов все больше занимает больше времени, например, модульный тест, скорее всего, будет очень коротким и простым для конкретной вещи, для которой он тестируется, в то время как E2E-тест будет длинным, сложным и, возможно, проверочным. ряд вещей
  • Тесты выполняются все дольше, например. E2E-тест будет выполняться намного дольше, чем модульный тест.
  • Ценность тестов возрастает (по мере того, как они становятся ближе к отражению реальных пользовательских сценариев), как бы мы ни были осторожны при написании модульных тестов, они принципиально не проверяют код так, как это делает реальный пользователь. бы. Чем более «реалистичен» и «удобен» тест, тем ценнее он
  • Выполнение тестов все больше и больше зависит от условий окружающей среды, например, в модульных тестах сторонние и внешние сервисы будут имитироваться, тогда как в E2E-тесте вы не будете имитировать и, следовательно, потребуется окружающая среда должна быть в хорошем рабочем состоянии

Проще говоря, чем раньше в жизненном цикле разработки вы что-то тестируете, тем дешевле будет и запустить тест, и и устранить обнаруженные проблемы. Однако тем менее вероятно, что тест действительно имитирует то, как Пользователь будет работать с системой.

Тестирование и бессерверные архитектуры

Итак, что все это означает для моего бессерверного проекта? Одно из ключевых преимуществ Serverless заключается в том, что вы можете сохранить каждую составную часть простой. Каждая функция FaaS должна иметь одну обязанность и один побочный эффект. Поэтому его код также должен быть простым. А это очень важно для тестирования.

Модульные тесты для функций FaaS

В подавляющем большинстве случаев процесс работы функции FaaS будет выглядеть примерно так:

  • Ввод: получение полезной нагрузки (событие, запрос API).
  • Проверить полезную нагрузку (правильно ли она структурирована, соответствует ли определенным бизнес-правилам)
  • Займитесь бизнес-логикой
  • Побочный эффект: выполнение побочного эффекта (сохранение чего-либо в базе данных, отправка события, возврат ответа и т. д.)

Поскольку функция FaaS настолько проста, я всегда рекомендую выполнять простые тесты черного ящика. Не приступайте к модульному написанию модульных тестов для каждого метода в функции, это просто приведет к хрупким тестам, которые терпят неудачу каждый раз, когда вы вносите изменения в код. Мы этого не хотим.

Написав модульные тесты, которые предоставляют функцию FaaS с известным вводом (полезной нагрузкой) и ожиданием известного результата (побочный эффект), можно достичь высокого охвата (~ 100%) с несколькими тестами, возможно, 8 или 10, в зависимости от количества сценариев проверки.

Главное в этом подходе то, что места для маневра не так много. Вы не можете увеличить количество тестов, если не будете тестировать что-то несколько раз (не делайте этого!). Но также вы не можете уменьшить количество тестов, не приведя к отсутствию охвата. Это упрощает принятие решений по модульным тестам:

  • Достигли ли тесты целевого охвата проекта?
  • Да. Были сделаны.
  • Нет. Добавьте отсутствующие тестовые сценарии черного ящика.

Именно на этом этапе вы тестируетеBusiness Logic.

Системное интеграционное тестирование (SIT)

Теперь, когда код функции FaaS протестирован, нам нужно проверить наше развертывание. Существуют инструменты для локального выполнения этого типа тестов, однако я рекомендую всегда выполнять системные интеграционные тесты в действительном развертывании.

SIT-тестирование после развертывания должно быть достаточно простым, если вы используете инфраструктуру как код и конвейер CI/CD, чтобы убедиться, что ваши развертывания полностью автоматизированы. После завершения развертывания нам нужно убедиться, что оно работает так, как задумано.

Итак, что мы тестируем? Что ж, начнем с того, что мы НЕ тестируем. Мы НЕ тестируем бизнес-логику. Модульное тестирование покрыло нашу бизнес-логику, поэтому повторное тестирование после развертывания является ненужным дублированием. Вместо этого мы должны сосредоточиться на проверке среды развертывания, то есть «соединительной ткани».

  • Разрешения: можно ли вызывать нужные нам облачные сервисы
  • Маршрутизация: доходят ли события/запросы до места назначения

Здесь следует сосредоточить внимание на наименьшем количестве возможных сценариев, которые охватывают все ресурсы, используемые проектом.

Возьмем, к примеру, конечную точку API, которая запускает функцию, которая, в свою очередь, записывает сообщение в очередь.

Внутренние биты функции уже были протестированы модульными тестами, поэтому мы не тестируем их здесь. Это означает, что требуется только один сценарий SIT.

Опять же, тестирование «черного ящика» — это то, что нужно. Достаточно простого теста, который вызывает конечную точку API, а затем проверяет очередь на наличие сообщения. Даже не проверяйте содержимое сообщения (эта бизнес-логика была рассмотрена в модульных тестах).

Убедитесь, что сценарии SIT максимально изолированы. Универсальная истина хороших тестов заключается в том, что когда тестовый сценарий терпит неудачу, он неявно указывает, какая часть системы неисправна.

Сквозные тесты (E2E)

После того, как все эти тесты были проведены в теории, мы должны быть уверены, что система работает! Проблема в том, что хотя комбинация модульных тестов и SIT обеспечивает хорошее покрытие системы, ни один из этих тестов не действует как пользователь.

Сквозные тесты должны имитировать взаимодействие пользователей с системой. Для этого каждый сценарий E2E, скорее всего, будет пересекать несколько вертикальных срезов. Это делает тесты E2E дорогими, они занимают много времени и иногда могут быть нестабильными.

Это основная область, на которую следует обратить внимание при выборе баланса…

Поиск баланса

Я уверен, что вы слышали о Пирамиде тестирования. По сути, это графическое представление типов тестирования, о которых я упоминал ранее. Он представляет типы в пирамиде, чтобы отразить, что более ранние тесты должны быть самыми многочисленными, а более поздние тесты должны быть меньше:

Используя подход, описанный выше, количество написанных сценариев Unit и SIT достаточно хорошо предписано. Существует логическое максимальное количество тестов для достижения покрытия без дублирования.

Таким образом, сокращение количества тестов — это решение о том, какие части системы не тестировать. Другими словами, «мне все равно, не работает ли эта бизнес-логика» или «мне все равно, не могу ли я вызвать эту конечную точку API». Иногда это будет нормально, особенно в случае проверки концепции или небольшого хобби-проекта. Но на самом деле для большинства систем выбор невелик. Мне нужна уверенность, что вся моя бизнес-логика работает и что вся моя «соединительная ткань» работает.

Рычаг, чтобы потянуть

Самое большое пространство для маневра при разработке вашего подхода к тестированию будет в сквозных тестах. Для достижения максимального охвата в тестах E2E часто требуется множество сценариев, МНОГО. Не несколько десятков, а потенциально сотни или тысячи.

Подумайте об этом, первый сценарий, вероятно, может охватывать довольно большую часть системы, и, возможно, второй. Но к тому времени, когда вы дойдете до сценария 1000, вы доберетесь до очень определенного уголка системы. При добавлении большего количества сценариев E2E отдача уменьшается.

Вспомните, сколько сценариев E2E вы пишете:

  • чем дольше их писать
  • тем больше времени требуется для их обслуживания
  • чем дольше выполняются тесты
  • тем больше вероятность того, что дефект приведет к множественным сбоям теста (что усложнит расследование)

Все эти факторы окажут существенное влияние на ваши показатели DORA. Итак, решение принять здесь:

Какое минимальное количество сквозных сценариев мне нужно, чтобы достаточно* быть уверенным в том, что моя система работает.

*Достаточная уверенность означает, что я знаю, что основные пути пользователя работают, и что у меня достаточно возможностей наблюдения, чтобы быстро узнать, если что-то пойдет не так в продакшене.

Неправильный шестиугольник тестирования

Поэтому, когда мы создаем бессерверные системы, наш подход к тестированию может оставаться довольно простым.

  • Используйте метод тестирования черного ящика для модульных тестов и SIT.
  • (Обычно) стремитесь к ~100% покрытию кода модульными тестами
  • (Обычно) стремитесь к ~100% охвату соединительной ткани при СИТ
  • (Очень редко) стремиться к 100% охвату кода в E2E

Отрегулируйте количество сквозных тестов в соответствии с вашим профилем риска. Другими словами, если вы очень не склонны к риску, то у вас будет большее количество сценариев E2E, вместо пирамиды тестирования у вас будет «неправильный шестиугольник тестирования»!

Подведение итогов

Стремление к 100% охвату модульным тестированием, вероятно, является спорным. Но позвольте мне объяснить мои рассуждения. Я предпочитаю стремиться к высокому уровню покрытия, а затем явно и намеренно исключать определенные файлы или фрагменты кода из отчета о покрытии. Это более разумно, чем снижать уровень глобального покрытия для всего проекта.

Кроме того, могут быть проекты, в которых достижение ~ 100% покрытия приводит к очень длительным тестам. Не хотелось бы вам об этом говорить, но в таком случае ваш проект, вероятно, слишком велик. Требуемый Когнитивный потенциал для поддержания проекта, вероятно, слишком высок. Разделите проект с помощью Domain Driven Design.

Удачного тестирования.