Привет, Хабр! Меня зовут Катерина, я инженер по автотестированию в команде онлайн-кинотеатра PREMIER и сегодня я хотела бы поделиться с вами своим опытом в написании API автотестов на Python. Я работаю в сфере автотестирования уже довольно давно и на практике встречаю проекты из разных сфер деятельности (банкинг, ритейл, телекоммуникации, строительство, развлечения и др.) зачастую, работая над ними, я сталкивалась с одной общей проблемой - код автотестов был тяжелым в понимании и плохо масштабируемым. Приходилось его капитально рефакторить, а это совсем невесело;) В этой статье  я хочу поделиться советами, как сделать ваш код более «чистым», легким в понимании и расширении. Мне всегда хочется думать, что тот, кто будет работать с моим кодом в будущем, будет думать обо мне и моём коде в положительном ключе, а не ругаться на него. Если вы разделяете мою философию, прошу под кат. Прежде чем начать писать сам автотест, очень важно  изучить документацию по вашему проекту и сделать сценарии, по которым вы будете создавать автотесты. В большинстве случаев автотестировщики пишут автотесты по уже готовым сценариям, поэтому на данном пункте мы долго останавливаться не будем, а вот изучение документации перед написанием автотестов является неотъемлемой частью процесса разработки. Только с таким знанием мы сможем написать качественные и полнофункциональные автотесты, которые будут эффективно проверять работу нашего программного продукта.  На какие вопросы нужно получить ответы, прочитав документацию по api методу:  Название метода? Что делает данный метод и для чего он нужен? На какой версии API доступен данный метод? Есть ли различия и какие в зависимости от версии API? И есть вообще версии API? Закрыт ли авторизацией данный метод? Если да, то какой? На каких устройствах доступен данный метод и какие есть ограничения в зависимости от устройства? Какой тип запроса используется для данного метода? (POST, GET и др.) Какой адрес метода? Есть ли параметры в адресе метода?  Если есть тело запроса, какие поля используются в теле запроса? Какие данные принимают данные поля? Какой тип данных используется в полях? Какие заголовки используются для запроса, какие параметры и какие поля - обязательные? Есть ли пример успешного запроса? Какой код успешного ответа? Есть ли пример успешного ответа? Присутствует ли описание полей ответа? Присутствует ли описание возможных ошибок, их код и описание? Присутствует ли информация, в какую / из какой таблицы, полей БД API сохраняет / берет данные? (не везде нужно - только там, где мы имеем доступ к БД) Не стоит забывать о том, что документация изменчива, порой аналитики (и не только они) вносят туда правки, что-то меняется, что-то удаляется, что-то добавляется и всегда стоит держать руку на пульсе - следить за актуальной документацией и вовремя вносить правки в свои автотесты.   Конечно, бывают случаи, когда документация очень скудная или её попросту нет, к сожалению, такое бывает, и я не раз с таким сталкивалась. В таких случаях стоит обратиться к “хранителю знаний”. Им может быть аналитик, разработчик или тестировщик, который уже сталкивался с этим методом до вас. Главное - найти этого человека и попросить поделится знаниями. Документация изучена, кейсы написаны - пришло время писать код! В первую очередь стоит подумать об архитектуре проекта, конечно, только если вы пишете его с нуля, так как в противном случае - об этом уже позаботились ваши предшественники и вам остается только адаптироваться под уже существующую архитектуру и код стайл ;) Но все же если вы стали первопроходцем и пишете проект с нуля, то проработка архитектуры тестового фреймворка будет вашей стартовой точкой, и от того, как вы его спроектируете, зависит вся ваша последующая работа и тех, кто будет работать вместе с вами над этим проектом. Давайте рассмотрим несколько ключевых аспектов, которые помогут вам построить правильную архитектуру для написания автотестов API. Выбор языка программирования является первым и одним из самых важных шагов в разработке тестового фреймворка. Рекомендуется выбрать язык, в котором у вас есть опыт разработки или который наиболее удобен для вашей команды. Наиболее популярными языками для разработки тестовых фреймворков являются Python, Java, Ruby и Type Script. Так как данная статья посвящена автотестам на Python, то ниже приведу основные плюсы использования языка Python для написания автотестов: Простота и ясность кода: Python имеет простой и понятный синтаксис, который упрощает чтение и понимание кода тестов. Это особенно важно для команды автотестирования, которая может легко сотрудничать и поддерживать код тестов. Большое количество библиотек и фреймворков: Python имеет обширное сообщество разработчиков, что привело к созданию большого количества библиотек и фреймворков, которые могут быть использованы для написания автотестов. Некоторые популярные фреймворки включают PyTest, unittest, Selenium, Appium и другие, которые облегчают создание и выполнение автотестов. Платформенная независимость: Python является интерпретируемым языком, что означает, что он может работать на разных операционных системах, таких как Windows, Linux и macOS, без необходимости внесения изменений в код тестов. Это позволяет использовать автотесты на разных платформах с минимальными усилиями. Расширяемость: Python является языком с открытым исходным кодом и имеет большое количество сторонних модулей и пакетов, которые можно легко импортировать и использовать в автотестах. Это позволяет расширить возможности тестирования. Интеграция с другими технологиями: Python хорошо интегрируется с другими технологиями и позволяет использовать различные инструменты и сервисы, такие как контейнеризация с Docker, системы управления версиями Git, CI/CD-платформы и другие. Структура проекта Важно определить правильную структуру проекта для удобства его использования и поддержки. Рекомендуется использовать модульный подход и разделить тесты, вспомогательные функции и конфигурацию на разные модули или пакеты. Это поможет вам легко находить и исправлять ошибки, а также добавлять новые тесты. Давайте рассмотрим два самых распространенных и удобных в использовании подхода: Разделение api методов на .py файлы Разделение api методов на папки/директории В данной реализации мы под каждый API метод создаем отдельный .py файл и в нём пишем тесты только на этот метод. Данные файлы будут располагаться в папке с тестами, которая, в свою очередь, будет лежать в корне проекта. Внутри файла мы можем создавать тестовые классы, например, класс с позитивными проверками и класс с негативными проверками, а внутри этих классов уже прописывать тестовые функции, где каждая функция будет отображать один тест-кейс.  Рассмотрим пример: Например, у нас есть POST метод Активации промокода /activate-promocode Мы создаем .py файл и называем его test_post_activate_promocode.py Далее уже внутри этого файла создадим два класса:class TestActivatePromocode: для позитивных кейсов и  class TestActivatePromocodeNegative: для негативных кейсов Что же, основной каркас создан, давайте приступим к наполнению и обогащению нашего тестового фреймворка. Для этого посмотрим на наши тест-кейсы, давайте начнем с позитивных кейсов, потому что, как известно, если позитивный тест не проходит, то в негативном и смысла нет)) Тот самый ответ, который ждут от кандидатов на собеседованиях когда просят протестировать карандаш;) def test_activate_promocode_successfully_activated(): Вот так, например, может выглядеть название позитивного кейса, когда нам нужно проверить успешную активацию промокода. О том, как именно стоит называть классы и тестовые функции мы поговорим чуть позже. В корне проекта будет располагаться ряд очень важных для проекта файлов: Логи Конфиги Тестовые данные Файл с общими проверками (checkers.py) Файл .gitlab-ci.yml Прежде всего, хотелось бы отметить такой важный пункт в автотестировании, как логирование. Если у вас не настроено логирование, вы никогда не узнаете, из-за чего ваше приложение упало и не сможете отследить, что привело к ошибке. Вы не сможете профилактически поглядывать в логи, чтобы следить за работоспособностью приложения и узнать состояние своей системы в момент времени N. Как правило, подключение логирования и его настройки производятся в файле с конфигурациями (в частности, conftest.py в pytest). А сами логи, результат работы логирования, хранятся в отдельном файле, например log_test.txt. При данной архитектуре проекта оба этих файла будут лежать в корне проекта.В языке Python основным инструментом для логирования является библиотека logging. Этот модуль входит в стандартную библиотеку, поэтому для его использования достаточно лишь импортировать ее в свой проект.  Подробно познакомится с этой библиотекой вы можете в официальной документацииКасаемо того какими должны быть логи - наверное, главное, чтобы они в принципе были)) Но также стоит не забывать о том, что излишнее логирование тоже плохо, оно перегружает вашу программу и делает поиск нужных логов в вашем огромном потоке информации сложным и времязатратным. Поэтому важно учитывать уровень логов (NOTSET, DEBUG, INFO, WARNING, ERROR, CRITICAL) и их необходимость, чтобы сделать поиск нужной вам информации простым и быстрым.  Также не стоит забывать и о наполнении логов, по умолчанию формат выглядит так: <УРОВЕНЬ>: <ИМЯ_ЛОГГЕРА>: <СООБЩЕНИЕ>. Но при желании вы можете расширить его дополнительной информацией, датой, названием файла с ошибкой, названием тестового метода, номером строки и так далее. Сделать это можно с помощью параметра format внутри функции basicConfig(). Например, если вы зададите вот такой формат: format = "%(asctime)s - %(levelname)s - %(funcName)s: %(lineno)d - %(message)s" То вывод ошибки будет выглядеть так: 2023-01-09 09:00:00,123 - ERROR - <module>:1 - Your message Не стоит впихивать в формат логов слишком много информации, стоит лишь указывать только ту информацию, которая действительно нужна вам и вашим коллегам.   Когда мы говорим о фреймворке по автотестированию с использованием pytest, есть несколько конфигурационных файлов, которые должны присутствовать для успешной работы.  Файл "pytest.ini": это основной файл конфигурации pytest, он используется для настройки pytest перед запуском тестов. В нем вы можете задать параметры, например, настройки вывода результатов или определить каталог с тестами. Также вы можете использовать его для задания маркеров. Файл "conftest.py": этот файл - настоящий друг! Он позволяет настраивать среду выполнения тестов. В нем вы можете определить фикстуры, которые будут использоваться в тестах. Фикстуры - это нечто, что вы "подготавливаете" перед запуском каждого теста, например, создание экземпляра класса или подготовка данных. Файл "pyproject.toml" (или "setup.cfg" для старых версий pytest): этот файл используется для настройки проекта в целом. В нем вы можете задать зависимости, указать ссылки на плагины или указать различные конфигурационные параметры pytest. Файл "tox.ini" (по желанию): этот файл используется, в случае, если нужно автоматизировать процесс запуска тестов на разных окружениях. Например, если у вас есть тесты, которые должны выполняться на разных версиях Python или различных операционных системах. Вот, собственно, основные файлы, которые должны присутствовать в фреймворке по автотестированию, использующем pytest. Разумеется, внутри этих файлов вы можете задать свои параметры и настройки, чтобы адаптировать фреймворк под свои потребности. Для тестовых данных лучше всего создать отдельную папку или отдельный файл, в котором мы будем их хранить и вызывать их непосредственно из самого теста.  Как правило, папка с тестовыми данными(файлами) лежит в корне проекта.  В данной папке могут храниться разные файлы с тестовыми данными .json или .csv.  Чаще всего это .py файлы, в которых хранятся данные, используемые в тестах, например: и уже непосредственно в самих тестах мы импортируем данный класс и обращаемся к нужному нам промокоду. Также тестовые данные могут браться из БД.Если приводить пример с промокодами, мы могли бы брать их из бд, например, для случая, когда нам нужно проверить успешную активацию промокода. В этом случае понятно, что просто файлом с промокодами не отделаешься, так как для каждого теста нужен свой актуальный промокод - а это значит их должно быть очень много. Для этого можно создать таблицу с актуальными промокодами (наполнить ее большим количеством актуальных промокодов и периодически её актуализировать) и при каждом запуске брать промокод из бд и после активации данного промокода из бд его удалять, так как он более не актуален. Также если реальная система работает с бд, то естественно, необходимо в автотестах проверить эту работу, чаще всего в этом случае делается копия боевой бд и заполняется тестовыми данными для тестирования. И наши автотесты будут брать, изменять и, если надо, удалять данные из этой тестовой бд. При работе с БД хорошей практикой считается использование ORM, например, SQLAlchemy.  Более подробно о данных функциях-проверках я расскажу чуть позже  Также в корне каталога расположится очень важный файл .gitlab-ci.yml с нашим пайплайном, но это уже другая история. В данной реализации под каждый API метод создается своя директория/папка и внутри этой папки хранятся все файлы, связанные конкретно с этим API. В этой папке будут лежать все файлы, которые относятся к данному методу. Например, все тестовые данные, которые мы будем использовать для тестирования данного метода, также тут могут храниться данные о базах данных, которые мы используем конкретно в этом методе, могут храниться какие-то конфиг-файлы. Ну и, конечно, самое главное – в данной папке будут храниться .py файлы, в которых будут написаны сами автотесты к данному методу. При данной архитектуре часто используется такой подход, что в каждом отдельном .py файле мы пишем ровно один тест. Выбор архитектуры зависит от многих факторов, и все их нужно учитывать при проектировании. Я привела примеры самых часто встречаемых архитектур тестовых фреймворков для API автотестов на Python. Для каждого проекта все настраивается индивидуально, под нужды команды. После того, как мы проработали архитектуру, можно приступать непосредственно к написанию самих тестов: Нейминг Комментарии Ассерты Методы-Чекеры Принципы разработки ПО Используйте осмысленные имена классов и функций, отражающие то, что вы тестируете. Пример: Если вы используете фреймворк для тестирования, убедитесь, что придерживаетесь его рекомендаций по именованию. Например, в pytest тестовые функции должны начинаться с test_ Используйте знаки подчеркивания для разделения слов в именах тестовых файлов и тестовых функций (snake_case). В тестовых классах следует использовать CamelCase - разделение с помощью заглавных буквНапример: файл test_user_login.py  Не используйте в именах сокращения, понятные только вамДавайте рассмотрим пример:def test_chk_act_code_with_old_apis_vers()  Имя данной функции содержит много сокращений chk, act, apis, verschk, act - такие сокращения сразу не поймешь, нужен контекст, время вникнутьв конкретном случае chk - это check, а act это activate vers - данное сокращение уместно, тк versions часто сокращают до ver def test_check_activate_code_with_old_api_vers() - вот так гораздо понятнее, а длина имени сильно не возросла Помните, что комментарии - это важная часть вашего кода. Пишите их также внимательно и аккуратно, как и код. Если хотите посмотреть, как их пишут профессионалы, вы всегда можете обратиться к методам официальных библиотек языка Python, например, методу json() Оставляйте комментарии только там, где это действительно необходимо. Не перегружайте код излишними комментариями. Комментируйте свой код на английском языке, чтобы он был доступен и понятен другим разработчикам. Это также поможет при работе с командой, компиляции документации и прочих ситуациях. Используйте комментарии для объяснения нетривиальных частей кода или алгоритма. Если ваш код решает сложную задачу или имеет неочевидные решения, поместите комментарий, который объяснит эту часть кода. Поддерживайте свои комментарии в актуальном состоянии. Если вы меняете код, обновите и комментарий, чтобы он соответствовал новому поведению. Это предотвратит путаницу и недопонимание. Используйте комментарии для того, чтобы временно отключить или объяснить временные исправления. Если вы временно модифицируете код, чтобы обойти ошибку или проблему, оставьте комментарий, объясняющий причину и указывающий на то, что это временное решение. Главное - не забыть потом этот комментарий убрать 🙂 Будьте конкретны в комментариях и избегайте общих выражений. Комментарии должны простым и ясным образом объяснять каждую часть вашего кода. Помните, что комментарии - это ваше дополнение к коду, а не замена для понятного и чистого кода. Хорошие комментарии могут помочь, но они не должны перекрывать недостатки в вашем коде. Никогда не закрывайте комментариями неиспользуемый код! У других авто тестировщиков не хватит храбрости, чтобы его удалить, так как они могут посчитать его важным и что он оставлен не зря и в итоге закомментированный код скапливается как куча мусора, и ни к чему хорошему это не приведет. Не стоит засорять свой рабочий продукт. Например, вы параметризируете свой тест и в какой-то момент решили отключить некоторые параметры, не стоит так делать - вы можете потом забыть их подключить обратно, и в итоге кто-то увидит вот такой код: и ему придется разбираться, что, зачем и почему оставлены комментарии, тратить время на вашу небрежность. Пишите сообщения об ошибке: в случае неудачной проверки, добавьте информативное сообщение об ошибке, чтобы было понятно, что именно пошло не так. Например, проверка на тип поля может выглядеть вот так:  Делайте проверки явными: вместо того, чтобы использовать сложные логические выражения, разделите их на отдельные проверки. Это поможет найти проблему быстрее, если она возникнет. Вот так писать точно не стоит))       Это искусственный пример, но такого типа ассерты я встречала и в реальной жизни Будьте точными: укажите все необходимые данные и ожидаемые результаты. Не оставляйте место для множественных интерпретаций. Если вы пишете проверку, которая часто используется, например, проверка статус кода, то такие проверки лучше вынести в функции.Например,  assert response.status_code == 200 лучше заменить методом def check_status_code(response, code=200): Тут стоит отметить что если есть возможность использовать значения по умолчанию - используйте, в данном случае используется status_code равный 200, так как он встречается в большинстве проверок. Это упростит и сократит код вызова функции.  check_status_code(response) - вот так может выглядеть вызов функции проверки статус кода равного 200.check_status_code(response, 400) - вызов при статус коде 400 Стоит избегать прямой проверки json  Например: Такой код опять же лучше заменить на метод или, если у вас есть четкая задача проверить именно полное совпадение json которой вы получили в ответе апишки с ожидаемым json, то стоит вынести его в тестовые данные. Давайте немного освежим в памяти базовые принципы по разработке ПО. Если вы никогда о них не слышали, то вы можете обнаружить, что некоторые из них вы применяете интуитивно. Принцип: Не придумывайте к задаче более сложного решения, чем ей требуется. Иногда самое разумное решение оказывается и самым простым.  Одна из основных ошибок начинающих разработчиков - использовать что-то очень сложное там, где это совершенно не нужно, чтобы показать свою “компетенцию” - что они знают эту сложную технологию и хотят это всем доказать. Изучить технологию - это значит не только изучить синтаксис, но и понять, где и когда ее действительно нужно использовать. Принцип: Не стоит дублировать код Дублирование кода не только пустая трата времени и ресурсов, но и дополнительная работа по его исправлению. Вам придется поддерживать одну и ту же логику сразу в нескольких местах, причем если вы измените код в одном месте, его нужно будет изменить и в другом. В большинстве случаев дублирование кода происходит из-за незнания вашего проекта (уже написанного кода, которого может быть очень много). Прежде чем что-либо писать, внимательно изучите код проекта, и, если есть сомнения можно уточнить у более опытных коллег. Возможно, уже где-то реализована функция, которая дублирует ваш код. Повторное использование кода, самое разумное решение.Например, если вы видите, что вы из теста в тест делаете одни и те же проверки, то стоит их вынести в отдельный метод (см. методы-чекеры) и в тестах вызывать этот метод. Принцип: Пишите только нужный код, который точно будет использоваться Очень простой и очевидный способ, о котором многие новички почему-то забывают. Когда пишете код, обязательно убедитесь, что он действительно будет использоваться, а не висеть мёртвым грузом. Если вы всё-таки решились на написание такого кода, потому что на 100% уверены, что в ближайшее время он пригодится вам или кому-то из ваших коллег, то обязательно оставьте к такому коду комментарий с пометкой о том, что на данный момент он не используется, но будет для таких-то целей и укажите примерную дату, когда код станет используемым. Данный комментарий будет очень важен вашим коллегам, да и вам тоже, в частности, например, для того, чтобы его случайно не удалили за ненадобностью. Данный принцип  часто используется при рефакторинге кода, когда мы рассматриваем весь код, классы, методы, и смотрим, используются ли данные классы, данные методы, нужны ли эти переменные или нет. И если мы видим, что код не используется и нигде не вызывается, то мы его просто удаляем. В случае же, если мы наталкиваемся на подобный неиспользуемый метод, но у него написан комментарий с пометкой о том, что скоро он действительно будет нужен, что его удалять не стоит, то, естественно, удалять его никто не станет, и он скоро сыграет свою роль в автотестах. Принцип Бритва Оккама - это философский принцип, который гласит, что из двух объяснений, объясняющих одно явление, необходимо выбирать наиболее простое и лаконичное. Применение этого принципа в автотестировании имеет несколько причин: Экономия времени и ресурсов: применение данного принципа позволяет сократить объем работы и сфокусироваться на наиболее значимых и существенных тестовых сценариях. Оптимальное использование ресурсов сохраняет время и средства, затрачиваемые на написание и поддержку автотестов. Увеличение читаемости и понятности автотестов: создание простых и четких автотестов позволяет легче и быстрее понять их назначение и реализацию. Это упрощает сопровождение автотестов и упрощает коммуникацию между членами команды. Снижение вероятности ошибок: сложные и запутанные автотесты могут стать источниками ошибок. Простые и прямолинейные автотесты более надежны, легче понять и проверить их выводы. Это позволяет предотвратить ошибки и улучшить качество автотестов. Адаптация к изменениям: использование Бритвы Оккама позволяет создавать автотесты, которые могут легко адаптироваться к изменениям в приложении. Простые автотесты менее чувствительны к изменениям, поэтому их проще поддерживать и обновлять. В целом, применение принципа Бритвы Оккама при написании автотестов помогает сделать их проще, более эффективными и надежными, что в конечном итоге повышает качество автотестов. Принцип: Избегайте преждевременной оптимизации Принцип Avoid Premature Optimization используется в программировании, включая написание автотестов, чтобы избегать оптимизации кода до тех пор, пока она фактически не станет необходимой. В контексте автотестов данный принцип означает, что в начале разработки автотестов необходимо фокусировать внимание на функциональности продукта и проверке корректности работы программного обеспечения. Это подразумевает создание тестовых сценариев, проверку основных функций и логики программы. Оптимизация автотестов, различные рефакторинги, включая улучшение скорости выполнения или уменьшение нагрузки на ресурсы, должна происходить только после достижения уверенности в корректной работе проверяемого продукта. Если заняться оптимизацией раньше, это может привести к затратам лишнего времени и ресурсов на улучшение кода, который впоследствии может быть изменен или даже удален. Кроме того, принцип Avoid Premature Optimization также может помочь в сохранении читаемости и понятности кода. Если сразу начать оптимизировать автотесты, то код может стать более сложным для понимания и поддержки, что усложнит работу вам и вашим коллегам. Использование принципа Avoid Premature Optimization позволяет сосредоточиться на проверке функциональности и корректности программного обеспечения, а оптимизацию оставить на более поздний этап разработки. Это помогает улучшить эффективность разработки и поддержки автотестов. В этой статье мы с вами рассмотрели различные способы, как сделать ваш код более «чистым», легким в понимании и расширении. В заключение хочу отметить, что написание автотестов - это непрерывный процесс обучения и совершенствования, поэтому экспериментируйте, изучайте, обменивайтесь опытом и всегда стремитесь к созданию более эффективных и продуктивных автотестов. Надеюсь, что эти советы помогут вам в достижении успеха в вашем тестировании!