Что пишут в блогах

Подписаться

Онлайн-тренинги

Что пишут в блогах (EN)

Разделы портала

Про инструменты

.
Ретроспективные уроки автоматизации: мастерство работы с данными
19.03.2021 16:54

Автор: Виктор Славчев (Viktor Slavchev)
Оригинал статьи
Перевод: Ольга Алифанова

К статьям вроде "Принцип трех А" я часто получаю комментарии вроде таких: "у нас проблемы с пересечением пользовательских данных в тестах", или "как бы вы управились с Х тестами, каждому из которых нужно наполнение данными".

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

У меня даже есть небольшое задание, которое я выдаю на собеседованиях автоматизаторов:

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

Вопрос: как вы настроите данные для проверок, чтобы успешно автоматизировать чекаут? Почему вы выбрали именно такой подход?

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

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

Немного контекста задачи

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

Худший способ подхода к данным

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

Наихудший способ – это регистрироваться через интерфейс для каждого прогоняемого теста или набора тестов.

Достоинства:

  • Практически всегда это хорошо сработает.
  • У вас есть новенький пользователь, никаких лишних зависимостей.

Недостатки:

  • Если вы создаете по пользователю на тест, это будет ОЧЕНЬ медленно.
  • Если вы создаете его до прогона набора тестов, вам нужно справляться с межтестовыми зависимостями (где закончил один тест, когда начнется другой).
  • К тому же вы столкнетесь с дополнительными трудностями, если вам нужно запускать тесты параллельно – все они будут зависеть от одинаковых пользователей. Управление состояниями будет кошмаром в этом случае.

Немного модифицированный наихудший возможный способ

Думаю, еще один плохой подход – это создание данных вне теста. К примеру, в моем тест-окружении есть пользователь viktor@test.com. Я знаю, что у него есть все нужные данные, и просто всегда авторизуюсь с viktor@test.com.

Достоинства:

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

Недостатки:

  • Если мне понадобится прогнать тесты в новом окружении/базе данных или в CI, у меня не будет viktor@test.com, и все тесты упадут.
  • Каждый раз при обнулении базы данных у меня будут задачи для выполнения вручную. Зачем нам это надо?
  • Мы создаем слишком стерильный случай, в нем нет контекста реальной жизни. У этого пользователя ничего не будет меняться, и это немного пугает.
  • Использование тестовых данных, на которых мы обычно тестируем и исследуем, может быть очень плохой идеей. Не знаю, как у вас, но из-за багов и мелких изменений, которые я вношу в своих пользователей, они похожи на монстра Франкенштейна. Не очень надежный вариант для тестов.
  • К тому же данные этого пользователя – к примеру, история заказов – будут нарастать с каждым прогоном тестов. Как вы будете с этим обращаться?

Сложность в обращении с данными

Надеюсь, вы понимаете, что тут нет единого правильного ответа – только менее неправильные. На самом деле все зависит от различных факторов.

Факторы, которые надо учитывать, прежде чем начать создавать данные для автоматизированных проверок

Вот факторы, с которыми я столкнулся на личном опыте:

  • Инфраструктура. Она определенно имеет значение, если вы тестируете в легковесном тест-окружении вроде docker, состоящем из виртуальных машин – по сравнению с мультикомпонентной настройкой, которая должна выполняться при каждом деплое. К тому же насколько велико и сложно ваше приложение, действительно ли его надо настраивать с нуля, или есть способ повторного использования каких-то частей? Можно ли использовать какой-нибудь вид кэширования для работы с данными, которые редко меняются – например, внешними библиотеками?
  • Тип тестируемой платформы. Не все платформы можно тестировать так, как веб. Если вы опытный тестировщик, такой вопрос у вас даже не стоит. К примеру, паттерн настройки данных перед каждым тестом или набором тестов немыслим в мобильном или десктоп-окружении, хотя для API-тестирования это довольно логичный вариант. Если вы используете неподходящий способ для ваших проверок, это довольно наивно.
  • Частота запуска проверок. Она может варьировать в зависимости от ваших процессов и релизного расписания. В любом случае стратегия создания данных будет сильно различной, если вы запускаете билды еженедельно, ежедневно, еженощно, или стартуете CI/CD билд для каждого коммита, чтобы достичь 20 релизов в день. Как я уже многократно говорил (и это одно из основных правил в тестировании) – никогда не хватайте то, что у кого-то работает, и не пытайтесь просто внедрить это у себя – это так не работает, как и ничто иное в жизни.

Начнем с конца: с состояний

Прежде чем я перечислю подходы, начнем с конца. Вам нужно раз и навсегда определиться (для своего контекста) – будете ли вы сбрасывать уже имеющиеся тестовые данные до определенного состояния, или удалять их полностью.

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

Зачем сбрасывать тестовые данные?

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

Зачем удалять тестовые данные?

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

Важный момент

Удаление или сброс данных после каждого тест-прогона может занимать время, в зависимости от размера вашей базы данных (и, возможно, скорости инфраструктуры). Поэтому ранее я сказал, что с этим надо сразу определиться. Некоторые компании и тестировщики сообщают, что выиграли 20-30% в скорости тестов, просто убрав этот шаг, поэтому будьте осторожны, это может дорого стоить.

Несколько различных подходов к созданию данных из моего опыта.

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

Самый тривиальный – SQL-скрипты.

Нравится вам это или нет, это, возможно, первое, что придет тестировщику в голову – как я уже сказал, это, в конце концов, три таблицы – можно просто написать несколько файлов с INSERT-запросами. Это просто и быстро: вы будете создавать данные при каждой необходимости.

Проблемы:

  • Вам нужно хранить эти SQL-скрипты в системе контроля версий.
  • Эти скрипты будут становиться очень хрупкими при каждом изменении в базе данных или используемой таблице. Представьте, что каждый раз, когда в таблицу добавляется новое обязательное поле, вам придется обновлять скрипты.
  • Это отлично работает с небольшими базами, но со временем добавляется больше данных, имеющих зависимости вроде ключей в других таблицах. Это неочевидно до прогона скрипта, и быстро превратится в настоящий кошмар.
  • И, напоследок – очень заманчиво основывать ваши тесты на пользователи, который создается через скрипты, но посмотрите на это с точки зрения ООП. Ваш тест Х пройдет и выполнит свою работу, только если выполнена скрытая зависимость в скрипте Y. Не то чтобы я был гуру программирования, но это кажется мне плохой практикой.

Использование фикстур данных

Использование фикстур данных немного превосходит прямые SQL-скрипты, но между этими подходами много общего.

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

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

Проблемы:

  • Это не решает проблему зависимости между данными и тестами, вы все еще пишете тесты, которые требуют Х сущностей.
  • Хотя создание объектов проще, чем ввод INSERT-запросов SQL, иногда создание новых фикстур может потребовать помощи от разработчика или кого-то с тем же уровнем навыков.
  • Вы можете соблазниться идеей выполнять тесты так: проверить, что отображено 5 объектов. Этот тест сломается, как только кто-то добавит что-нибудь в фикстуру. Проверки, падающие не из-за найденной ими проблемы – не то, что нам нужно в наших автотестах.

Подход "каждый за себя"

Этому подходу я, наверное, симпатизирую больше всего (возможно, и скорее всего, я пристрастен). Он базируется на изоляции тестов, принципе герметичности – он гласит, что каждый тест или набор должен самостоятельно отвечать за требуемые данные – к примеру, тест А создает данные, нужные ему для прогона, тест Б создает свои собственные данные, и так далее. Создание данных может происходить через некий внешний инструмент, вызов фикстуры, или неким аналогичным способом.

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

Конечно, это не серебряная пуля – у этого подхода тоже есть проблемы.

Проблемы:

  • Этот подход быстро станет огромной проблемой, если ваша база данных велика, и вам нужно множество данных. Будет очень сложно добавлять все требуемые сущности или необходимые для запуска определенного кейса данные.
  • Необходимость в хорошем инструменте создания данных потребует или внедрять процесс, имитирующий прод (в нем могут быть баги), или вам придется потратить значительное время на то, чтобы сделать нечто уже существующее. Обходной путь – попросить разработчиков о помощи, чтобы они предоставили вам инструмент с логикой прода.
  • Вам нужно очень ясно представлять, что вы будете делать с данными – их удаление после каждого теста может быть не очень хорошей идеей, а если их оставлять, то каждая запись должна быть уникальной, чтобы не возникало конфликтов. Хранение этих данных до конца прогона может стать проблемой, если тестов у вас много – но, с другой стороны, все, что угодно, становится проблемой при большом количестве тестов.
  • Это также не очень хорошая идея, если у вас большое количество операционных данных. Под операционными данными я подразумеваю данные, не относящиеся к конкретной проверке, но необходимые для работы приложения. К примеру, в нашем интернет-магазине находятся данные о локациях или магазинах, валютах, и тому подобная информация. Это может означать, что данные придется импортировать для каждого теста, хотя на самом деле достаточно добавить их в базу данных до старта тестов, и иметь дело только с данными, имеющими отношение к проверкам.

Спагетти-подход

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

Проблемы:

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

Комбинированный подход

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

Например:

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

Вообще не подход к созданию данных

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

Можно вообще разделить проверки и создание данных, если разместить их в двух разных приложениях. Одно будет синхронизировать данные с БД прода, чтобы получить 20-50 свежих и точных клиентских записей. Оно также займется обрезкой или заменой персональных данных, защищенных законом или другими требованиями.

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

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

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

Проблемы:

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

  • Подход требует значительных усилий по созданию соответствующей экосистемы.
  • Вам понадобится глубокий анализ данных персон, представленных вашими пользователями, и степени соответствия полученных учеток этим персонам. Если это большое приложение с большой пользовательской базой и различными настройками у каждого пользователя, то 20-50 учеток может и не хватить.
  • Придется принимать сложное решение, будете ли вы выполнять один тест с одним случайным пользователем, или все тесты со всеми пользовательскими профилями – тогда общее количество ваших тестов будет картезианским произведением тест-персон на различные конфигурации.
  • Каждый раз, когда я использовал жестко закодированные данные (фикстуры или SQL-скрипты), это прилетало бумерангом мне в лоб. Возможно, не сразу, но прилетало обязательно.
  • В одном из продуктов, с которым я работал, мы сделали отличный внутренний инструмент для создания данных. Он использовал бизнес-логику, выводя ее через PHP-пакет. Однако это требовало полноценной занятости старшего разработчика в течение нескольких дней, а также дополнительного времени на добавление новых функциональностей каждый раз, когда мы что-то меняли или добавляли.
  • Удаление тестовых данных, если БД запускается в контейнере – самоликвидирующаяся проблема – вы просто удаляете контейнер после прогона всех тестов. Конечно, нужно убедиться, что все проверки создают уникальных пользователей и данные, чтобы из-за повторов не падали тесты.

Плюсы и минусы из моего опыта:

Заключение

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

Удачи!

Обсудить в форуме