Conference for DevOps, Software Architects and Engineers. Regular price ends 27/09!
×Закрыть

Введение в культуру DevOps: выбираем стратегию тестирования

Это первая статья из серии «Введение в культуру DevOps». Предыдущий материал был вводным, этот посвящен тестированию. Рассмотрим, какие стратегии тестирования выбрать команде, которая старается культивировать у себя культуру DevOps.

Тестирование и требования

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

Не все тесты одинаково полезны. Если вы тестируете не поведение, а реализацию — тесты будут актуальны очень короткое время, пока реализация не будет заменена. И наоборот, если тестируется поведение модуля, то тесты будут актуальны до тех пор, пока модуль существует. Не стоит писать тесты на приватные методы. Это опять возвращает нас к тому, что мы тестируем поведение public-интерфейсов. Приватные методы — это только детали реализации. С точки зрения тестирования они нам не интересны.

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

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

Первый класс — это функциональные требования. Они предусматривают, как должна работать система. Хороший пример функциональных требований — описание того, что при нажатии на кнопку «Submit» должна появляться надпись «Ok». Это требование говорит, что должна делать система. Однако в таких требованиях ничего не сказано про то, как, скажем, быстро должна появиться эта надпись. Поэтому появился второй класс — нефункциональные требования. Они говорят, как это должна делать система. Если приходит требование, что запись должна появляться не менее чем за 1 секунду — это типичное нефункциональное требование. К нефункциональным относится все, что касается юзабилити, производительности, дизайна и т. д.

Теперь перейдем к видам тестирования. И тут я вынужден вставить комментарий от капитана очевидности, что тестирование бывает функциональное и нефункциональное. Также тестирование делится на ручное и автоматическое. Сразу отбросим ручное тестирование. Оно не вписывается в одну из главных практик DevOps «автоматизируй все, что можно». Следовательно, мы будем обращать внимание на автоматические тесты.

Типы автотестов

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

Мы будем сравнивать автотесты по следующим параметрам:

  • парадигма, исповедуемая тестами Black Box / White Box / Grey box тестирование;
  • скорость прохождения сферического единичного теста в вакууме;
  • затраты на разработку;
  • зависимость от среды исполнения.

Итак, автотесты делятся на следующие типы: Unit, Integration, End-To-End.

Unit

Это тесты, проверяющие public-интерфейсы отдельно взятого модуля. Они исповедуют концепцию white box testing, при которой человеку, который тестирует систему, доступен весь ее исходный код. Сразу оговорюсь, white box в некоторых случаях превращается в полноценный black box из-за того, что иногда модули рассматриваются именно как черные ящики с входящими и исходящими данными.

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

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

Если говорить о затратах на поддержание, юнит-тесты — самые стабильные при прохождении. Среда их выполнения синтетическая и не зависит от внешних факторов. То есть если юнит-тесты упали, то на 99% это ошибка приложения, а не какое-то нелепое стечение обстоятельств, когда что-то пошло не так, как хотелось.

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

Если подытожить, юнит-тесты — идеальный инструмент проверки качества кода, но не приложения. Они показывают разработчикам, где и что именно они поломали в режиме реального времени. Они должны запускаться на машине разработчика. Также они добавляются в прекоммит хуки или CI pipeline, дабы разрабы не коммитали заведомо нерабочий код.

Integration

Это тесты, проверяющие функциональность взаимодействия нескольких модулей одновременно. Они используют grey-box тестирование, при котором мы можем относиться к тестируемому объекту как к черному ящику и в тоже время дергать какие-то внутренние методы, лазить в базу и т. д.

Положа руку на сердце, практически все тесты, которые создаются разработчиками, являются интегрейшенами. Редко когда разраб упарывается и полностью изолирует систему под тестированием от абсолютно всех взаимодействий. И если API влияют — Integration тесты уже зависимы от окружения. Они уже показывают то, как вы умеете деплоить систему.

Разработка этого вида тестов уже не занимает столько времени, как нужно для Unit`ов. Однако эти тесты более хрупкие, потому что большое количество факторов влияет на их работу. Их прохождение занимает больше времени, нежели у юнит-тестов за счет взаимодействия с третьесторонними API. Теперь уже разработчик не может запускать эти тесты после каждого сохранения, потому как такой тест длится порядка нескольких секунд.

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

End-To-End

Это тесты, проверяющие всю функциональность в целом. Такой вид тестов использует концепцию черного ящика, при которой приложение представляет собой неведомую вещь, с которой мы взаимодействуем посредством публичных интерфейсов. Почему-то всегда Е2Е-тесты ассоциируются у большинства разработчиков с Selenium, хотя тестирование REST API средствами http-запросов — также типичный образец Е2Е-тестов, которые полностью укладываются в концепт черного ящика.

Е2Е-тесты проверяют работоспособность системы в целом. Прогон занимает достаточно много времени, поэтому их не гоняют локально. Я бы даже не рекомендовал запускать их после каждого пуша в центральный репозиторий. Тесты идеальны для запуска после слияний веток или относительно окружений sandbox или staging — для того, чтобы контролировать состояние приложения в ветках и на окружениях. Также при использовании микросервисной архитектуры приложения тесты могут покрывать сразу несколько микросервисов, что приведет к лучшему покрытию кода тестами.

E2E-тесты зависят от многих факторов и чрезвычайно хрупкие. Иногда письмо о заваленном билде может прийти только из-за того, что затупил браузер или же была проблема со связью на виртуалке (кстати, это утверждение справедливо и для Integration).

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

Покрытие кода тестами

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

Теперь давайте обсудим проценты покрытия тестов. Почему-то покрытие принято считать по линиям кода. То есть у нас есть 100 линий кода, тест (не важно какого типа) проходится по 60-ти из них — в итоге получаем покрытие 60%. Но значит ли это, что у нас все хорошо? Нет. Так как покрытыми могут быть именно те строчки, в которых вы определяете переменные, а код, содержащий логику, может быть просто пропущен. Специально для этого были придуманы другие метрики покрытия кода тестами.

Существуют такие метрики покрытия кода тестами:

  • По линиям. Описано выше.
  • По классам или методам. Класс или метод считается покрытым, если он хотя бы раз был вызван тестами. Эту метрику любят менеджеры, потому что обычно она показывает самые впечатляющие результаты.
  • По логическим веткам. Ветка считается покрытой, если в нее хоть раз заходил тест. То есть если у вас есть if с else — и тест попал только в одну из его логических ветвей, то покрытие такого кода будет равняться 50%. Эта метрика очень удобна для оценки того, насколько детально вы просчитали все варианты развития событий в ваших тестах. Имейте в виду, что ветками считаются if-else, switch-case, try-catch. Тернарники не считаются элементами, разветвляющими код.
  • По выражениям. Выражение считается покрытым, если оно выполнялось хоть раз при проходе тестом. Вот тут и считаются тернарники, так как они идут на одном уровне с обычными операторами.

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

DevOps и стратегия тестирования

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

DevOps-культура предлагает свести к минимуму ручную QA-работу, дабы иметь возможность релизиться как можно чаще, что безопаснее и стабильнее. В DevOps-культуре роль QA-инженеров смещается от тестеров к людям, которые следят за качеством проекта и помогают разработчикам в написании автоматических тестов, вырабатывают стратегию тестирования. Тут уже мало места для QA, которые не разбираются в технологических аспектах приложения, CI/CD процессах.

Стратегия тестирования — это план, который позволит вам работать с минимальной затратой времени, а следовательно, и денег. Стратегия тестирования для DevOps не должна звучать: «Мы используем Protractor и Jenkins». Это не план. Это всего лишь перечисление инструментов, которые используются вами. Главное вопросы, на которые должен отвечать план, — почему и зачем.

Мы должны ответить себе на следующие вопросы:

  • Какие ресурсы у нас обязательны к тестированию?
  • Каков будет результат успешного прохождения тестов? Какую информацию нам надо получить для успешной поставки или что может остановить поставку и заставить нас начать что-то поправлять?
  • Риски. Как наши тесты уменьшают их? И как это будет представлено команде?
  • Расписание. Когда мы будем прогонять те или иные виды тестов?
  • Кто будет отвечать за автоматизированные тесты? Кто будет их разрабатывать? Кто будет конечным получателем результатов тестов?
  • Что будет триггерить старт тестов? Когда команде ожидать начала тестирования?

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

При пуше кода в фича бранч должны прогоняться сьюты integration- и unit-тестов. Они определяют статус билда и возможность смерджить его в главную dev-ветку. При поломанном билде не должно быть возможности мерджа, а коммитер должен быть оповещен, что его коммит сломал ветку. Далее идет мердж в master или stage brunch и сборка там. При этом требуется прогонять все тестовые наборы, включая E2E.

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

Если все наборы на тестовых окружениях прошли нормально, осуществляется поставка продукта на продакшен и снова прогон E2E тестовых наборов уже на продакшен окружении. В дополнение к вышеперечисленным тестовым наборам на продакшене должен быть осуществлен прогон performance, penetration и других нефункциональных тестов. Если что-то пошло не так — осуществляется откат и оповещение всех задействованных в процессе деплоймента и разработки. Причем заметьте, что откат должен быть тоже протестирован E2E и другими тестами.

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

Выводы

Автоматизированное тестирование — одна из неотъемлемых практик DevOps-культуры. Она позволяет контролировать исходный продукт и оперативно устранять ошибки, начиная от процесса разработки и заканчивая процессом деплоя на продакшен-окружение.

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

LinkedIn

27 комментариев

Подписаться на комментарииОтписаться от комментариев Комментарии могут оставлять только пользователи с подтвержденными аккаунтами.

Не хватает ясности по поводу интеграционного и E2E тестирования в статье, Имеется ввиду тестирование одного (микро-)сервиса в изоляции, или взаимодействие с другими (включая сторонние) сервисы? Это как-бы меняет картину полностью и по большому счету является совершенно разными тестами.

Редко когда разраб упарывается и полностью изолирует систему под тестированием от абсолютно всех взаимодействий.

ммм, это как бы первый шаг в автотестировании — тестирование в изоляции. Подгребаем базу / pub-sub middleware докером, мокаем внешние системы WireMock или подобным — и полетели.

ИМХО функциональное тестирование без изоляции — это четкий антипаттерн и дожно избегаться. Без изоляции тестируется именно интеграция и возможно нефункциональщина типа производительности.

Иногда письмо о заваленном билде может прийти только из-за того, что .... была проблема со связью на виртуалке (кстати, это утверждение справедливо и для Integration).

докер пробовали? Не то, чтобы совсем ото всех проблем избавит, но явно станет меньше и быстрее.

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

Почему-то покрытие принято считать по линиям кода.

«Линии кода» — это те самые, которых семь зелёных перпендикулярных, две из которых красные? (facepalm)

> А тестирование как таковое — это всего лишь сопоставление требований заказчика с текущим состоянием продукта.

Что-то мне подсказывает, что это не так.

Nick, по-перше дякую за статтю!
В мене виникли питання щодо:

При пуше кода в фича бранч должны прогоняться сьюты integration- и unit-тестов. Они определяют статус билда и возможность смерджить его в главную dev-ветку. При поломанном билде не должно быть возможности мерджа, а коммитер должен быть оповещен, что его коммит сломал ветку. Далее идет мердж в master или stage brunch и сборка там. При этом требуется прогонять все тестовые наборы, включая E2E.

Які ти бачиш підводні камені в тому щоб на фіче бранчі проганяти повний набір тестів включно E2E? Хто відповідальний за фікс червоних Е2Е тестів, коли вони проганяються на master? Адже якщо проганяти Е2Е тести на фіче бранчі, то розробник що ламає його, може або пофіксити свій код, або підправити сам Е2Е тест.

’підправити сам е2е тест’ - правильно, не код треба фіксить. Фіксить треба тести, що не проходять)))

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

В десятку, нещодавно довелося правити інтеграційний тест, який ніколи не тестував код, але ассерт завжди був зеленим.

1) Подводные камни следующие — первое это как всегда лень и время. Ждать не любит никто. Во вторых это хрупкие тесты, опять же мало кто любит разбираться почему тесты упали, когда легче всего обвинить QA инженера, парней ответственных за окружение и т.д. Мое мнение что E2E тесты должны писать разрабы — тогда эти все вопросы и претензии автоматом уйдут.
2) Чинить тесты опять же должны разрабы — тут должен действовать закон — чинит тот кто поломал, в красную бранч мердж должен быть закрыт
3) Мое мнение что надо прогонять все тестовые наборы — потому как мерджи отнюдь не способствуют уменьшению багов :)

Тобто у випадку коли Е2Е тести швидкі + їх повинно бути небагато за визначенням й стабільні з якимось рівнем порогу зеленості, то з твого боку немає інших причин не проганяти їх на фічі бренчі?

Про уровень порога зелености — он у меня только один, хотя бы один тест завален — завелен весь тестовый прогон. Прогонять имеет смысл всегда, прогон показывает насколько затронули изменения функционал. Однако ждать многие не любят. бывает просто команду не устраивает что каждый разраб будет ожидать(хотя тут можно работать и паралельно) результатов прогона тестов перед тем как смерджить свои изменения в масте/дев/стейдж. Но мое мнение надо прогонять всегда в фичабранче при создании пулл реквеста — дальше все идет на обсуждение команды.

У випадку якщо на фічі бренчі немає можливості проганяти всі Е2Е тести, як варіант завжди мати Е2Е смок тест сьют що виконується значно швидше, але все одно залишається додатковим індикатором до юніт та інтеграційних, чи вартий пул реквест бути замердженим чи ні. Nick, дякую що відповів на питання.

Я бы тут сказал не дополнительным индикатором. В моей практике было такое что юниты хорошо отрабатывали а сама фича лежала. Е2Е это оценка того как вы делаете фичу в целом.

Имейте в виду, что ветками считаются if-else, switch-case, try-catch. Тернарники не считаются элементами, разветвляющими код.

Я всегда считал тернарный оператор синтаксических сахаром для if-else-then. Почему это не считается логической веткой?

В следующей статье покажу что это не так. Тернарник является оператором а не логической веткой. Во всяком случае если говорить про языки PHP/JS/Python/C#/C++/Java

Извините, но кроме модного слова в заголовке статьи о DevOps здесь ничего нет. То что вы описали практически никакого отношения к девопс не имеет. Можно взять практически любую обзорную статью по автоматизации тестирования и прочитать там тоже самое.
А еще особенно понравилась вот эта фраза:

В DevOps-культуре роль QA-инженеров смещается от тестеров к людям, которые следят за качеством проекта и помогают разработчикам в написании автоматических тестов, вырабатывают стратегию тестирования

Интересно кто же этим занимается ))))

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

Перво с чего надо начать, это научить писать осмысленные, информативные и сопровождаемые тесты. Как правило писать таковые вообще мало кто умеет среди девелоперов.

Давайте вместе постараемся это исправить.

отлично что у вас есть такая мотивации — вот вам небольшой хинт основанный на личном наблюдении на будущее
Большинству понятно о TDD, CI, coverage tools, branch policies — об это пишут везде и говорят на всех конфернециях различной направленности, не только devops;
В том, что тестировать разбираться меньше, но тоже пытаються разбираться понемногу и задумываются о том зачем нужны unit, зачем integration, а зачем end-to-end хотя иногда и здесь могут перестараться и сделать больше проблем чем пользы, если не учитывать особенности обьекта тестирования.
Еще меньше, к сожалению, пытается понять почему их тесты сложно сопровождать, почему их тесты неинформативны при появлении проблем, пропускают баги и т.д. В худшем варинте заканчиваеться это тем, что тесты просто не пишут — потому что это отнимает неожиданно время. На эту тему общаются значительно меньше, особенно в девелоперской среде потому, что сильно недооценивают сложность этого процесса. Подтверждением тому частота упоминания тестов в контексте, того как их втулить в CI — что несравнимо простая задача в контексте обеспечения качества продукта и отсуствие достойного обсуждения «скучной» проблемы качества тестов. Вам как devopsy знакома эта проблема?

Мне эта проблема знакома причем знакома с разных сторон: как DevOps, Dev, QA да еще и как Product Owner — Добавляйтесь на фейсбук — в личке обсудим данную проблему

Я по тексту статьи не увидел рекоммендаций по этому поводу конкретно(как контролировать, как стандартизировать, обучать в конце концов, типичные use cases, анти-паттерны и способы их решения и т.д.) — если дополните или напишете отдельную статью о вашем опыте введения эффективного процесса написания автоматизированных тестов будет интересно ознакомиться.
Еще такой вопрос — вы с позиции всех эти ролей считаете, что это задача, которую ранее выполняли другие роли(QA Lead/Dev Lead/Architect т.д.) теперь отнесли к девопсу — по каким-то особы причинам? Какое видите взаимоотношения в этой иерархии в вопросе внедрения стратегии тестирования с другими? как с точки зрения результата, он должен стать лучше хуже или это необходимая мера — наделить девопса обязанностями, в которых скажем так другие узкоспециализированные специалисты QA профиля могут разбираться обьективно куда лучше.

Головна проблема з тестуванням в тому, що ізпочатку відсутні спеки на дані, сервіси, протоколи етц. Нема чітких критеріїв відповідності об’єкта тестування вимогам специфікації, бо самої специфікації теж нема.

А как вы выполняете задачу, не зная критериев приёмки?

Представьте себе что вы решили сэкономить на поездке в такси. Зачем устанавливать приложение, вводить кучу данных, а иногда и обновлять ОС. Можно просто взмахнуть рукой на дороге. Останавливается жигуль и вас вежливо спрашивают — Куда? Вы называете адрес. Если вам говорят — Дарогу покажыш? Добро пожаловать в мир гибкой разработки без спецификаций ))

Здесь нет ничего нового для тестировщика. Не стоит приплетать термин DevOps ко всему подряд. Есть специалисты (вероятно, на вашем проекте также) которые разбираются в тестировании лучше и знакомы с этим не только на high level.

Подписаться на комментарии