Привет, Хабр! Меня зовут Катерина, я инженер по автотестированию в команде онлайн-кинотеатра 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-платформы и другие.
Структура проекта
Важно определить правильную структуру проекта для удобства его использования и поддержки. Рекомендуется использовать модульный подход и разделить тесты, вспомогательные функции и конфигурацию на разные модули или пакеты. Это поможет вам легко находить и исправлять ошибки, а также добавлять новые тесты.
Давайте рассмотрим два самых распространенных и удобных в использовании подхода:
1. Разделение api методов на .py файлы
В данной реализации мы под каждый API метод создаем отдельный .py файл и в нём пишем тесты только на этот метод. Данные файлы будут располагаться в папке с тестами, которая, в свою очередь, будет лежать в корне проекта.
Внутри файла мы можем создавать тестовые классы, например, класс с позитивными проверками и класс с негативными проверками, а внутри этих классов уже прописывать тестовые функции, где каждая функция будет отображать один тест-кейс.
Рассмотрим пример:
Например, у нас есть POST метод Активации промокода /activate-promocode
Мы создаем .py файл и называем его test_post_activate_promocode.py
Далее уже внутри этого файла создадим два класса:class TestActivatePromocode:
для позитивных кейсов и
class TestActivatePromocodeNegative:
для негативных кейсов
Что же, основной каркас создан, давайте приступим к наполнению и обогащению нашего тестового фреймворка.
Для этого посмотрим на наши тест-кейсы, давайте начнем с позитивных кейсов, потому что, как известно, если позитивный тест не проходит, то в негативном и смысла нет)) Тот самый ответ, который ждут от кандидатов на собеседованиях когда просят протестировать карандаш;)
def test_activate_promocode_successfully_activated():
Вот так, например, может выглядеть название позитивного кейса, когда нам нужно проверить успешную активацию промокода.
О том, как именно стоит называть классы и тестовые функции мы поговорим чуть позже.
В корне проекта будет располагаться ряд очень важных для проекта файлов:
Логи
Прежде всего, хотелось бы отметить такой важный пункт в автотестировании, как логирование. Если у вас не настроено логирование, вы никогда не узнаете, из-за чего ваше приложение упало и не сможете отследить, что привело к ошибке. Вы не сможете профилактически поглядывать в логи, чтобы следить за работоспособностью приложения и узнать состояние своей системы в момент времени 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 файлы, в которых хранятся данные, используемые в тестах, например:
class TestDataPromocodes:
promocode_contains_invalid_symbols = '1iuz1d9fqd!!' # Промокод, который содержит недопустимые символы
not_exist_promocode = '343yuyu7u8' # Несуществующий промокод
wait_for_ready_promocode = '8r22pn2d0k' # Промокод, срок которого еще не доступен
time_off_promocode = 'nqvdwpqf31' # Промокод с истекшим сроком действия
activated_promocode = 'mjgwnv67' # Промокод, который ранее был активирован
и уже непосредственно в самих тестах мы импортируем данный класс и обращаемся к нужному нам промокоду.
from Test_data.test_promocode_data import TestDataPromocodes
…
promocode = TestDataPromocodes.valid_promocode_with_payment
Также тестовые данные могут браться из БД.
Если приводить пример с промокодами, мы могли бы брать их из бд, например, для случая, когда нам нужно проверить успешную активацию промокода. В этом случае понятно, что просто файлом с промокодами не отделаешься, так как для каждого теста нужен свой актуальный промокод - а это значит их должно быть очень много. Для этого можно создать таблицу с актуальными промокодами (наполнить ее большим количеством актуальных промокодов и периодически её актуализировать) и при каждом запуске брать промокод из бд и после активации данного промокода из бд его удалять, так как он более не актуален.
Также если реальная система работает с бд, то естественно, необходимо в автотестах проверить эту работу, чаще всего в этом случае делается копия боевой бд и заполняется тестовыми данными для тестирования. И наши автотесты будут брать, изменять и, если надо, удалять данные из этой тестовой бд.
При работе с БД хорошей практикой считается использование ORM, например, SQLAlchemy.
Файл с общими проверками (checkers.py)
Более подробно о данных функциях-проверках я расскажу чуть позже
Также в корне каталога расположится очень важный файл .gitlab-ci.yml с нашим пайплайном, но это уже другая история.
2. Разделение api методов на папки/директории
В данной реализации под каждый API метод создается своя директория/папка и внутри этой папки хранятся все файлы, связанные конкретно с этим API.
В этой папке будут лежать все файлы, которые относятся к данному методу. Например, все тестовые данные, которые мы будем использовать для тестирования данного метода, также тут могут храниться данные о базах данных, которые мы используем конкретно в этом методе, могут храниться какие-то конфиг-файлы. Ну и, конечно, самое главное – в данной папке будут храниться .py файлы, в которых будут написаны сами автотесты к данному методу. При данной архитектуре часто используется такой подход, что в каждом отдельном .py файле мы пишем ровно один тест.
Выбор архитектуры зависит от многих факторов, и все их нужно учитывать при проектировании. Я привела примеры самых часто встречаемых архитектур тестовых фреймворков для API автотестов на Python. Для каждого проекта все настраивается индивидуально, под нужды команды.
Написание автотестов
После того, как мы проработали архитектуру, можно приступать непосредственно к написанию самих тестов:
Нейминг
-
Используйте осмысленные имена классов и функций, отражающие то, что вы тестируете.
Пример:
class TestLoginFunctionality:
def test_successful_login(self):
...
def test_invalid_credentials(self):
...
Если вы используете фреймворк для тестирования, убедитесь, что придерживаетесь его рекомендаций по именованию. Например, в pytest тестовые функции должны начинаться с
test_
-
Используйте знаки подчеркивания для разделения слов в именах тестовых файлов и тестовых функций (snake_case). В тестовых классах следует использовать CamelCase - разделение с помощью заглавных букв
Например:файл
test_user_login.py
class TestUserLogin:
def test_successful_login(self):
-
Не используйте в именах сокращения, понятные только вам
Давайте рассмотрим пример:def test_chk_act_code_with_old_apis_vers()
Имя данной функции содержит много сокращений
chk, act, apis, vers
chk, act
- такие сокращения сразу не поймешь, нужен контекст, время вникнуть
в конкретном случаеchk
- этоcheck
, аact
этоactivate
vers
- данное сокращение уместно, ткversions
часто сокращают доver
def test_check_activate_code_with_old_api_vers()
- вот так гораздо понятнее, а длина имени сильно не возросла
Комментарии
-
Помните, что комментарии - это важная часть вашего кода. Пишите их также внимательно и аккуратно, как и код.
Если хотите посмотреть, как их пишут профессионалы, вы всегда можете обратиться к методам официальных библиотек языка Python, например, методу json()
def json(self, **kwargs):
r"""Returns the json-encoded content of a response, if any.
:param \*\*kwargs: Optional arguments that ``json.loads`` takes.
:raises requests.exceptions.JSONDecodeError: If the response body does not
contain valid json.
"""
if not self.encoding and self.content and len(self.content) > 3:
# No encoding set. JSON RFC 4627 section 3 states we should expect
# UTF-8, -16 or -32. Detect which one to use; If the detection or
# decoding fails, fall back to `self.text` (using charset_normalizer to make
# a best guess).
encoding = guess_json_utf(self.content)
if encoding is not None:
try:
return complexjson.loads(self.content.decode(encoding), **kwargs)
except UnicodeDecodeError:
# Wrong UTF codec detected; usually because it's not UTF-8
# but some other 8-bit codec. This is an RFC violation,
# and the server didn't bother to tell us what codec *was*
# used.
pass
except JSONDecodeError as e:
raise RequestsJSONDecodeError(e.msg, e.doc, e.pos)
try:
return complexjson.loads(self.text, **kwargs)
except JSONDecodeError as e:
# Catch JSON-related errors and raise as requests.JSONDecodeError
# This aliases json.JSONDecodeError and simplejson.JSONDecodeError
raise RequestsJSONDecodeError(e.msg, e.doc, e.pos)
Оставляйте комментарии только там, где это действительно необходимо. Не перегружайте код излишними комментариями.
Комментируйте свой код на английском языке, чтобы он был доступен и понятен другим разработчикам. Это также поможет при работе с командой, компиляции документации и прочих ситуациях.
Используйте комментарии для объяснения нетривиальных частей кода или алгоритма. Если ваш код решает сложную задачу или имеет неочевидные решения, поместите комментарий, который объяснит эту часть кода.
Поддерживайте свои комментарии в актуальном состоянии. Если вы меняете код, обновите и комментарий, чтобы он соответствовал новому поведению. Это предотвратит путаницу и недопонимание.
Используйте комментарии для того, чтобы временно отключить или объяснить временные исправления. Если вы временно модифицируете код, чтобы обойти ошибку или проблему, оставьте комментарий, объясняющий причину и указывающий на то, что это временное решение. Главное - не забыть потом этот комментарий убрать ????
Будьте конкретны в комментариях и избегайте общих выражений. Комментарии должны простым и ясным образом объяснять каждую часть вашего кода.
Помните, что комментарии - это ваше дополнение к коду, а не замена для понятного и чистого кода. Хорошие комментарии могут помочь, но они не должны перекрывать недостатки в вашем коде.
-
Никогда не закрывайте комментариями неиспользуемый код! У других авто тестировщиков не хватит храбрости, чтобы его удалить, так как они могут посчитать его важным и что он оставлен не зря и в итоге закомментированный код скапливается как куча мусора, и ни к чему хорошему это не приведет. Не стоит засорять свой рабочий продукт.
Например, вы параметризируете свой тест и в какой-то момент решили отключить некоторые параметры, не стоит так делать - вы можете потом забыть их подключить обратно, и в итоге кто-то увидит вот такой код:
@mark.parametrize('device', [ 'mobile_ios', # 'mobile_android', # 'apple_tv', # 'tablet_ios', # 'tablet_android', # 'android_tv', # 'smart_tv', # 'browser' ])
и ему придется разбираться, что, зачем и почему оставлены комментарии, тратить время на вашу небрежность.
Ассерты
-
Пишите сообщения об ошибке: в случае неудачной проверки, добавьте информативное сообщение об ошибке, чтобы было понятно, что именно пошло не так.
Например, проверка на тип поля может выглядеть вот так:
assert isinstance(name, str), f"type of field name is not str, it is {type(name)}"
-
Делайте проверки явными: вместо того, чтобы использовать сложные логические выражения, разделите их на отдельные проверки. Это поможет найти проблему быстрее, если она возникнет.
Вот так писать точно не стоит))
assert x == 4 if (b + a) == 11 else (
x == 7 if (b == a if a % 2 == 1 else b == a - 1) else x == 10), 'Incorrect x value'
Это искусственный пример, но такого типа ассерты я встречала и в реальной жизни
Будьте точными: укажите все необходимые данные и ожидаемые результаты. Не оставляйте место для множественных интерпретаций.
Методы-Чекеры
Если вы пишете проверку, которая часто используется, например, проверка статус кода, то такие проверки лучше вынести в функции.
Например, assert response.status_code == 200
лучше заменить методом def check_status_code(response, code=200)
:
assert response.status_code == code, f"Ожидался status_code = {code}, получен status_code = {response.status_code}"
Тут стоит отметить что если есть возможность использовать значения по умолчанию - используйте, в данном случае используется status_code равный 200, так как он встречается в большинстве проверок. Это упростит и сократит код вызова функции.
check_status_code(response)
- вот так может выглядеть вызов функции проверки статус кода равного 200.check_status_code(response, 400)
- вызов при статус коде 400
Стоит избегать прямой проверки json
Например:
assert response.json() == {"activationStatus": 6, "message": "Указанный код не существует"}
Такой код опять же лучше заменить на метод или, если у вас есть четкая задача проверить именно полное совпадение json которой вы получили в ответе апишки с ожидаемым json, то стоит вынести его в тестовые данные.
Принципы разработки ПО
Давайте немного освежим в памяти базовые принципы по разработке ПО.
Если вы никогда о них не слышали, то вы можете обнаружить, что некоторые из них вы применяете интуитивно.
KISS (Keep It Simple, Stupid)
Принцип: Не придумывайте к задаче более сложного решения, чем ей требуется.
Иногда самое разумное решение оказывается и самым простым.
Одна из основных ошибок начинающих разработчиков - использовать что-то очень сложное там, где это совершенно не нужно, чтобы показать свою “компетенцию” - что они знают эту сложную технологию и хотят это всем доказать. Изучить технологию - это значит не только изучить синтаксис, но и понять, где и когда ее действительно нужно использовать.
DRY (Don’t Repeat Yourself)
Принцип: Не стоит дублировать код
Дублирование кода не только пустая трата времени и ресурсов, но и дополнительная работа по его исправлению. Вам придется поддерживать одну и ту же логику сразу в нескольких местах, причем если вы измените код в одном месте, его нужно будет изменить и в другом.
В большинстве случаев дублирование кода происходит из-за незнания вашего проекта (уже написанного кода, которого может быть очень много). Прежде чем что-либо писать, внимательно изучите код проекта, и, если есть сомнения можно уточнить у более опытных коллег. Возможно, уже где-то реализована функция, которая дублирует ваш код. Повторное использование кода, самое разумное решение.
Например, если вы видите, что вы из теста в тест делаете одни и те же проверки, то стоит их вынести в отдельный метод (см. методы-чекеры) и в тестах вызывать этот метод.
YAGNI (You Aren’t Gonna Need It)
Принцип: Пишите только нужный код, который точно будет использоваться
Очень простой и очевидный способ, о котором многие новички почему-то забывают. Когда пишете код, обязательно убедитесь, что он действительно будет использоваться, а не висеть мёртвым грузом. Если вы всё-таки решились на написание такого кода, потому что на 100% уверены, что в ближайшее время он пригодится вам или кому-то из ваших коллег, то обязательно оставьте к такому коду комментарий с пометкой о том, что на данный момент он не используется, но будет для таких-то целей и укажите примерную дату, когда код станет используемым. Данный комментарий будет очень важен вашим коллегам, да и вам тоже, в частности, например, для того, чтобы его случайно не удалили за ненадобностью.
Данный принцип часто используется при рефакторинге кода, когда мы рассматриваем весь код, классы, методы, и смотрим, используются ли данные классы, данные методы, нужны ли эти переменные или нет. И если мы видим, что код не используется и нигде не вызывается, то мы его просто удаляем. В случае же, если мы наталкиваемся на подобный неиспользуемый метод, но у него написан комментарий с пометкой о том, что скоро он действительно будет нужен, что его удалять не стоит, то, естественно, удалять его никто не станет, и он скоро сыграет свою роль в автотестах.
Бритва Оккама
Принцип Бритва Оккама - это философский принцип, который гласит, что из двух объяснений, объясняющих одно явление, необходимо выбирать наиболее простое и лаконичное. Применение этого принципа в автотестировании имеет несколько причин:
Экономия времени и ресурсов: применение данного принципа позволяет сократить объем работы и сфокусироваться на наиболее значимых и существенных тестовых сценариях. Оптимальное использование ресурсов сохраняет время и средства, затрачиваемые на написание и поддержку автотестов.
Увеличение читаемости и понятности автотестов: создание простых и четких автотестов позволяет легче и быстрее понять их назначение и реализацию. Это упрощает сопровождение автотестов и упрощает коммуникацию между членами команды.
Снижение вероятности ошибок: сложные и запутанные автотесты могут стать источниками ошибок. Простые и прямолинейные автотесты более надежны, легче понять и проверить их выводы. Это позволяет предотвратить ошибки и улучшить качество автотестов.
Адаптация к изменениям: использование Бритвы Оккама позволяет создавать автотесты, которые могут легко адаптироваться к изменениям в приложении. Простые автотесты менее чувствительны к изменениям, поэтому их проще поддерживать и обновлять.
В целом, применение принципа Бритвы Оккама при написании автотестов помогает сделать их проще, более эффективными и надежными, что в конечном итоге повышает качество автотестов.
Avoid Premature Optimization
Принцип: Избегайте преждевременной оптимизации
Принцип Avoid Premature Optimization используется в программировании, включая написание автотестов, чтобы избегать оптимизации кода до тех пор, пока она фактически не станет необходимой.
В контексте автотестов данный принцип означает, что в начале разработки автотестов необходимо фокусировать внимание на функциональности продукта и проверке корректности работы программного обеспечения. Это подразумевает создание тестовых сценариев, проверку основных функций и логики программы. Оптимизация автотестов, различные рефакторинги, включая улучшение скорости выполнения или уменьшение нагрузки на ресурсы, должна происходить только после достижения уверенности в корректной работе проверяемого продукта. Если заняться оптимизацией раньше, это может привести к затратам лишнего времени и ресурсов на улучшение кода, который впоследствии может быть изменен или даже удален.
Кроме того, принцип Avoid Premature Optimization также может помочь в сохранении читаемости и понятности кода. Если сразу начать оптимизировать автотесты, то код может стать более сложным для понимания и поддержки, что усложнит работу вам и вашим коллегам.
Использование принципа Avoid Premature Optimization позволяет сосредоточиться на проверке функциональности и корректности программного обеспечения, а оптимизацию оставить на более поздний этап разработки. Это помогает улучшить эффективность разработки и поддержки автотестов.
Подведем итоги:
В этой статье мы с вами рассмотрели различные способы, как сделать ваш код более «чистым», легким в понимании и расширении. В заключение хочу отметить, что написание автотестов - это непрерывный процесс обучения и совершенствования, поэтому экспериментируйте, изучайте, обменивайтесь опытом и всегда стремитесь к созданию более эффективных и продуктивных автотестов. Надеюсь, что эти советы помогут вам в достижении успеха в вашем тестировании!
Комментарии (7)
Litovsky83
27.11.2023 07:32Добрый вечер. Спасибо статью!
В python из коробки неудобно пользоваться словарём, особенно при большой вложенности. Используете ли вы библиотеки, например attrs, marshmallows и подобные ? Как вы валидируете ответ в response, например обязательные поля, тип и длину ?
DevKate Автор
27.11.2023 07:32Для валидации ответа в response мы не используем сторонние библиотеки (attrs, marshmallows и подобные) - мы используем свой подход. response мы представляем как объект — экземпляр класса. Мы парсим ответ (стандартными методами из коробки) в набор атрибутов и их значений. Под конкретный api метод мы создаём свой класс, в котором описываем множество обязательных полей, множество дополнительных полей и множества типов в виде:
fields_int = {'count', 'offset', 'limit'} fields_list = {'games', 'players'}
Далее проверяем на наличие обязательных и дополнительных полей, сравниваем множество атрибутов, пришедших в ответе с множеством, описанным к классе. Если какие атрибуты отсутствуют в ответе, то выводим ошибку с выводом разницы этих множеств. Далее следует проверка на типы - тут мы проверяем типы всех значений согласно объявленным в классе, например, значения атрибутов '
count
', 'offset
', 'limit
' из примера выше мы проверяем на то, что они типа int. И третий метод-проверка - это проверка данных - тут мы проверяем и длину полученного значения, например, если есть лимит по длине значения, и при необходимости значение. Например, мы сделали запрос с определённым id пользователя и в ответе хотим проверить, что значениеid
равно переданному в запросе, по такие значения мы передаём в качестве переменной в метод проверки, например,assert_data(id)
. В случае наличия вложенных объектов, мы такие объекты тоже представляем в виде класса. Это удобно тем, что такие классы легко переиспользовать. Например, есть респонс-объект и информацией о подписке и там есть объект "Тариф" - мы его выделяем в отдельный класс (поля, типы, проверки значений) и когда к нам приходит респонс-объект "Информация о пользователе", в котором есть такой же объект "Тариф", то мы его заново не пишем, а используем уже описанный класс "Тариф". Возможно, при большом уровне вложенности данный подход не подойдёт, но в нашем случае подходит очень хорошо. Также мы сделали класс с общими проверками (на наличие полей и типов), от которого мы наследуемся в классах-респонсах, чтобы у них, как у наследников были эти методы и не приходилось бы каждый раз писать их заново.
danilovmy
Хотя и полезно, спасибо, но хотелось бы ещё и рекомендации по самим библиотекам - что и как юзать. Почему pytest а не unittest, почему классы а не функциональные тесты, что лучше factory-boy или mommy/bakery. А чего б не взять tavern или postman это наше все. Если тест интеграции с Aws брать moto/boto или писать своё. И в конце - а где авто сборщик документации тестов а'ля swagger?
DevKate Автор
Почему pytest вместо unittest или tavern- pytest предлагает более простой, гибкий и мощный подход к автотестированию на Python, как по мне, unittest лучше подходит для юнит-тестов, чем для автотестов.
Что лучше factory-boy или mommy/bakery - мне кажется, тут всё индивидуально, каждая из них имеет свои преимущества и недостатки, которые могут варьироваться в зависимости от ваших конкретных потребностей и предпочтений.
Такие инструменты как postman всё-таки чаще используются для ручного тестирования и автотестировщики его используют скорее как подспорье или перепроверку результатов, чем как основной инструмент.
Почему классы, а не функциональные тесты - если речь о том, что на каждую функцию свой тест, то это относится с юнит-тестированию. Если вопрос в том, что один тест - один класс, то это далеко не всегда так - например, в разделе "Разделение api методов на .py файлы" я описываю случаи, когда в одном классе может быть много тестовых функций - в одном .py файле может быть, например, 2 класса один с позитивными и один негативными проверками, внутри которых будут описаны соответствующие тестовые функции. Разделение на классы в данном случае удобно для того, чтобы можно было быстрее и легче запустить скоупом все позитивные или все негативные проверки.
Если тест интеграции с Aws брать moto/boto или писать своё - в своей практике я использовала Boto3, так как нашей целью было интеграционное тестирование с реальными сервисами AWS. Но если у Вас такой необходимости нет, можно использовать и Moto, который эмулирует AWS API локально и позволяет вам запускать автотесты без использования реальных сервисов AWS. Если Ваш функционал совсем специфичный и его никак не покрыть возможностями существующих библиотек - то, можно подумать в сторону чего-то своего.
А где авто сборщик документации тестов а'ля swagger - документация по API методам (swagger) - как правило, реализуется на стороне разработки, в не в автотестах. Что касаемо документирования автотестов - тут скорее речь пойдёт про такие инструменты, как allure - а как настроить интеграцию с ним и как лучше писать тесты и аллюр аннотации, чтобы отчёты были максимально информативными - дело отдельной статьи))
Andrey_Solomatin
Как разработчик не соглашусь. unittest лучше подходит для тестирования встроенных библитек Питона, потому что он часть этих библиотек, что не требует внешних зависимостей.
А так пайтест имеем более простой синтаксис и много фич.
Исторически unittest сделали чтобы было, а pytest чтобы удобно. 10 лет назад это были день и ночь, сейчас unittest сократил отставание, но всё еще сильно позади.
Поиск и запуск тестов через pytest умеет находить и правильно запускать код написанный с помощь unittest.
DevKate Автор
С этим я абсолютно согласна! Сама я пишу юнит-тесты с использованием pytest. Но среди разных проектов видела случаи, где разработчики использовали unittest. А вот в автотестировании я никогда не видела, чтобы кто-то использовал unittest, поэтому так и написала.