Валерий | AQA Engineer | Автотестирование на Python | REST, gRPC, GraphQL
Kanalga Telegram’da o‘tish
Сделаю из тебя крутого AQA инженера на Python. • Преподаю лучшие тренинги по автоматизации тестирования API • Senior Python developer | AQA lead, 7 лет в IT
Ko'proq ko'rsatish1 510
Obunachilar
-124 soatlar
+47 kunlar
+830 kunlar
Postlar arxiv
Сегодня выходной!
Наконец-то пришло время, когда можно отдохнуть от будничной суеты и задуматься: как же лучше провести это время? Я всегда считал выходные отличной возможностью поразмышлять о том, что действительно важно и полезно. И, конечно, не могу не обсудить свой курс по автоматизации тестирования REST API на Python!
Много раз мне говорили, что стоимость моих курсов при таком уровне качества материалов — это настоящая находка, и пришло время повысить цену. Но я прекрасно понимаю, что у кого-то, возможно, не хватило времени или решимости записаться на курс. Поэтому я решил оставить старую цену до 25 августа! Это ваш шанс получить много знаний и навыков, которые станут отличной основой вашей карьеры в автоматизации тестирования.
🔍 Почему стоит выбрать именно этот курс?
1. Практическая направленность: Вы не только изучите теорию, но и сможете сразу применить полученные знания на практике. Мы будем работать над реальным проектом, который навсегда останется в вашем GitHub.
2. Автоматизация по самым современным стандартам: Вы освоите процесс автоматизации тестирования REST API с использованием Python, что сделает вас востребованным специалистом на рынке труда. Мы будем использовать самые проверенные и актуальные инструменты.
3. Поддержка и взаимодействие: Я всегда готов ответить на ваши вопросы и помочь, чтобы вы не чувствовали себя одинокими в обучении. Те, кто у меня учился, прекрасно знают, что, так как я работаю на себя, мне критически важно, чтобы обучение понравилось. Я отвечаю максимально оперативно и практически 24/7.
4. Инструменты и технологии: Мы изучим самые необходимые и проверенные временем инструменты для автоматизации тестирования, которые сейчас используются в индустрии. В результате у вас будет репозиторий в GitHub с настроенным pipeline для запуска тестов и публикации отчетов.
5. Архитектура и её построение: Мы будем использовать и объединять несколько паттернов проектирования. Вы увидите и поймете полную структуру фреймворка. Он будет построен не на "быстром" решении, а станет масштабируемым промышленным фреймворком, использующим лучшие практики.
Курс “Автоматизация тестирования REST API Advanced” является второй ступенью из трёх курсов. Первый — Starter, который охватывает основы большей части курсов по автоматизации тестирования на Python, затем Advanced, который учит писать автотесты красиво и раскрывает все тонкости автоматизации тестирования API, и в будущем я планирую Professional, который будет включать настройку IDE, создание собственных библиотек и использование инструментов для соблюдения единообразия и чистоты кода фреймворка при наличии нескольких контрибьюторов, а также инструментов генерации кода. В том числе я научу пользоваться своим инструментом для генерации кода фреймворка API тестов, который вы видели на видео. Все курсы связаны между собой и опираются на предыдущую часть.
Не упустите возможность прокачать свои навыки и стать экспертом в автоматизации тестирования! Используйте старую цену до 25 августа и запишитесь на курс.
Промокод
ADVANCED15 на скидку: 4485 р.
📩 Если у вас есть вопросы или нужна дополнительная информация, пишите в личные сообщения. Жду вас на курсе! 🚀
P.S Начало обучения уже 2-го сентября.+1
Что значит написать тул и чем тул отличается от автотеста?
Давайте немного раскроем этот момент.
Автотест проверяет какую-то функциональность, тогда как тул выполняет вспомогательные задачи.
Например, самым простым примером тула является линтер. Что делает линтер? Он может проанализировать ваш код, найти в нем уязвимости, забытые и неиспользуемые импорты и принты. Линтер не является автотестом, он просто помогает искать ошибки в вашем коде, указывая на конкретные строки.
Существуют также форматтеры, которые нужны для того, чтобы привести код проекта в соответствие с едиными правилами, чтобы код выглядел единообразно и, например, поддерживал последнюю версию синтаксиса Python. То есть мы запускаем тул, и он автоматически форматирует наш код. Да, это может делать и IDE, но среда разработки правит только один файл, а форматтер обрабатывает весь проект.
Есть и другие тулы для генерации кода, например, openapi-generator, который генерирует код клиентской библиотеки, и это тоже не является автотестом.
Вот видео работы тула, который разрабатываю я: он генерирует код для всего тестового фреймворка. То есть это тоже не автотест, он не проверяет бизнес-функционал, но подготавливает всю архитектуру проекта. А дальше вы уже сами собираете автотест из нужных шагов.
Надеюсь, теперь вопрос о том, чем тул отличается от автотеста, более раскрыт.
Моя новая роль!
Вчера я общался с моим новым руководителем.
Как я и говорил, в OZON я буду отвечать за поддержку и сопровождение фреймворка разработки микросервисов на Python, но основной мой стрим, учитывая экспертизу в этом вопросе, — это стрим автотестирования. Что это значит?
Если раньше я занимался написанием инструментов для автотестирования факультативно и "по фану", теперь я буду заниматься этим профессионально.
Моя задача заключается в том, чтобы предложить новый фреймворк автотестирования на замену старому, не поддерживаемому. Эта задача сложнее административно, нежели технически, ведь определенная кодовая база уже есть, и навыки тоже. Намного сложнее убедить людей в очередной раз пересесть на новый фреймворк и переписать ТЫСЯЧИ тестов.
Существующий фреймворк умеет очень много, поэтому с наскока эту задачу тоже не решить.
Какие я планирую шаги?
Первое — собрать фокус-группу, чтобы понять потребности людей в автотестировании.
Второе — придумать простые шаги для миграции существующих тестов на новый фреймворк.
Третье — должна быть какая-то функциональность, которая будет критически важной для того, чтобы люди захотели перейти на новый фреймворк.
Эти задачи очень сложные и потребуют больших усилий. Посмотрим, как у меня получится.
Забегая наперед, буду ли я учить разрабатывать инструменты для автотестирования?
Да, у меня есть мысли на этот счет. Но это скорее будет про подходы к кодогенерации и обвязке. Все это я планировал включить в ступень Professional, осталось только найти на это время. 😊
Сегодня я перешел на темную сторону.
Точнее, выбрал путь горизонтального развития.
Это видео, которое мне очень нравится, можно дополнить еще одним кадром: мне 34 года, и я Senior Python Developer.
С 2018 года я прошел большой путь. Как и все, когда-то боролся со страхом, не поздно ли в 28 переходить в IT.
Как и многие, я выбрал стартом своей карьеры путь тестировщика.
Кто-то может сказать: “Зачем нужно было идти в тестирование, если планировал в разработку?”
Отвечу: не планировал, просто так сложилась жизнь. Рад я этому? Я думаю, да.
На протяжении своей карьеры я всегда задумывался, как можно ускорить всю эту рутину. Практически через полгода работы я уже начал активно автоматизировать все свои телодвижения: сравнение данных, создание папок и перенос файлов. Меня просто сам процесс просил приложить к этому руку, затем я занимался поддержкой существующих инструментов автоматизации.
Когда я пришел в OZON, у меня была задача поднять и развернуть автотесты на API. У меня не было наставника, который бы мне рассказал, как правильно это сделать, и более того, курсов по автоматизации тестирования API не существовало. Все мои архитектурные решения были разработаны, обкатаны и переписаны самостоятельно. Я перепробовал кучу вариантов и способов комплектации тестовых фреймворков, удобства их поддержки и сопровождения.
Я выяснил, что тесты на API очень шаблонны, и большую часть создания кодовой базы можно автоматизировать. В 2020 году, когда был коронавирус, я жил один, и у меня было предостаточно времени, чтобы с этим разобраться, тогда я написал свой первый инструмент для генерации автотестов. В то время таких инструментов не было, и это был очень большой прорыв.
Так как ничего готового не существовало, я начал разрабатывать свою платформу и свои библиотеки, выделяя их в отдельные пакеты и учась переиспользовать. Это был прекрасный опыт.
Довольно быстро меня стали приглашать проводить собеседования. Я обучал своих коллег писать код правильно и красиво. В момент, когда я понял, что нахожусь в профессиональном тупике и не понимаю, как дальше расти, мне познакомили с Head of QA одной из команд. От него я подчерпнул тему кодогенерации, он рассказал о существующих инструментах. Это дало мне новый скачок в развитии, и я стал думать и внедрять эти инструменты, настраивать пайплайны и строить тестовые фреймворки на их основе.
Моя компания ставила мне амбициозные задачи, которые сильно развивали меня. Задачи по проведению нагрузочного тестирования прокачивали во мне знания в многопоточности и асинхронном программировании. Затем я работал над написанием тулов для парсинга и подготовки тестовых данных.
После получения роли Lead QA Engineer я развернул свой сервис (back-end), собрал команду, и за год мы создали, на мой взгляд, шикарный сервис, который помогает в подготовке тестовых данных и тестировании бизнес-задач. Это также прокачало во мне навыки асинхронного программирования, обучения, сопровождения и ведения процесса разработки.
Для развития нашего сервиса мне понадобился функционал, которого еще не было в Python Platform. Я разработал инструмент для генерации HTTP-клиентов для нашего сервиса. По фану для себя написал генератор фреймворка автотестов.
Мои руководители поняли, что я уже давно вырос из написания автотестов и простого тестирования бизнес-задач. Программировать мне нравится больше, чем проверять, и на текущий момент наиболее логичным кажется мой переход в разработку и занятие любимым делом.
Я уже писал для Python Platform, поэтому особых проблем с переходом на новую должность у меня не было.
Является ли переход в новую команду для меня развитием? Безусловно. Если раньше я создавал инструменты и сервисы для тестировщиков, теперь буду разрабатывать и для разработчиков. Это точно сделает меня сильнее как инженера.
В общем дальше больше! Run Boy Run
Буду ли я дальше вести канал про тестирование и автоматизацию, 100%, у меня собран очень большой и качественный бэкграунд, который может помочь очень большому количеству начинающих и продолжающих инженеров.
Так ли сложно изучать автоматизацию тестирования?
На самом деле, всё гораздо проще, чем кажется! У меня были ученики, которые проходили полноценный курс, имея за плечами только базовые знания языков программирования и мастер-класс (REST API Starter).
На успешный результат обучения влияет множество факторов. Часть из них зависит от преподавателя, а часть — от ученика.
Как преподаватель, я опираюсь на один интересный факт, который прочитал в интернете: фокус человеческого внимания в среднем составляет 20 минут. И я с этим согласен, потому что со мной всё работает именно так. Я не люблю живые вебинары, где преподаватель растекается мыслью по древу в течение трёх часов. Смотреть такие вебинары — это огромная мука! На мой взгляд, почти любую тему автоматизации тестирования, если заранее спланировать лекцию и подготовить материал, можно уложить в эти 20 минут. Например, на моём курсе продолжительность видео составляет от 2 до 20 минут.
Второй фактор, влияющий на обучение, — это применимость знаний. Я не люблю решать абстрактные задачи ради задач. Материал должен быть актуальным и применимым, а в идеале — помогать решать рабочие задачи в моменте. На моём обучении ученики довольно часто пишут фреймворки параллельно с прохождением курса.
Третий фактор — домашние задания. Их должно быть достаточно, и они должны иметь подходящий уровень сложности, чтобы ученику приходилось немного подумать и включать навыки поиска решений в интернете или в том же ChatGPT. Автоматизатор — это не печатная машинка, это человек, который может решать ручные задачи с помощью автоматизации. Я должен показать ученику набор инструментов и как ими пользоваться.
Четвёртый фактор — это фидбек и опыт преподавателя. Эти два понятия неразрывны. Человек должен иметь опыт использования различных инструментов, знать, какие инструменты когда и для чего применимы или не применимы. Он должен иметь множество собранных граблей, чтобы оградить ученика от большинства ошибок и сократить его путь от мидл-позиции до сеньор-позиции. Фидбек должен быть понятным, ёмким и, главное, быстрым. В одном из отзывов ученик написал, что ему друзья так быстро не отвечают, как это делаю я!
Остальное зависит от ученика. Здесь я бы выделил два необходимых для успеха качества: упорство и дисциплину. Нужно просто мобилизоваться на 4 недели и маленькими шагами смотреть видео и выполнять задания. Главное — не сдаваться и задавать вопросы, если что-то непонятно. К сожалению, с этим у многих возникают сложности.
Мне кажется, при соблюдении этих правил у всех всё должно получиться!
Ну а чтобы понимать нафига мы это делаем, достаточно посмотреть вилки на SDET и AQA, тогда мотивация будет более прозрачной)
Пишите в комментариях, что для вас является самым важным. Какие аспекты курсов вас больше всего бесили? С какими сложностями вы сталкивались при обучении?
Способы проектирования архитектуры API тестов.
ЧАСТЬ 3. BaseCase Pattern
Сегодня поговорим про еще один паттерн, который я называю BaseCase Pattern.
Его можно использовать как самостоятельно, так и в прекрасной комбинации с паттерном, о котором я говорил в части 2.
https://t.me/AQA_Engineer/145
Суть его заключается в следующем:
Создается класс BaseCase. В нем мы можем инициализировать необходимые клиенты, писать общие фикстуры, которые могут переиспользоваться.
Главное, что необходимо сделать — это наследовать класс с тестами от базового тест-кейса (BaseCase). Все классы-наследники будут иметь доступ к методам и свойствам, описанным в родительском классе.
class BaseCase:
@pytest.fixture(scope="session")
def http_dm_api_account_service(self):
client = HTTPDmApiAccountFacade()
return client
class TestsPostV1Account(BaseCase):
@pytest.fixture(scope="session")
def my_client(self, http_dm_api_account_service):
"""
Фикстура приведена для примера показать, что можем использовать в тестах фикстуры из BaseCase
Тут можно добавить свою логику, например авторизацию и т.д.
"""
return http_dm_api_account_service
def test_post_v1_account(self, my_client) -> None:
my_client.account_api.post_v1_account_for_test()
def test_post_v1_account_base(self, http_dm_api_account_service) -> None:
http_dm_api_account_service.account_api.post_v1_account_for_test()
Плюсы:
1. Прост в применении
2. Удобное масштабирование, достаточно создать класс наследник для своего сьюта и реализовывать там вспомогательную логику.
3. Легко читается и поддерживается.
4. Можно использовать, как отдельно инкапсулируя вспомогательную логику внутри класса наследника, так и использовать в качестве фасада для удобного доступа к клиентам
Минусы:
1. Тоже может возникнуть дублирование кода, ситуация схожая с Handler Pattern, но в отличие от него, достаточно удобно сделать перенос в класс более высокого уровня.
Резюме, паттерн классный, я стараюсь его использовать вместе с Helper Pattern.
Пишите какие паттерны встречали вы, или может есть что добавить к текущему посту.Repost from 📚 ProTestingInfo 🔷 Канал по тестированию 📚
Что я изучала по автоматизации.
1,5 года я была автоматизатором на Java, потом 4 года перерыв.
Сейчас изучаю Python около года и буду применять свои знания на проекте.
И все же у меня на прошлых проектах были отлично выстроены процессы по автоматизации, поэтому на своих консультациях я делюсь своим опытом с коллегами и менти.
———
То, что я изучала:
Java
Бесплатно
- Aвтоматизация тестирования API с JAVA - YouTube
- книга изучаем JAVA - https://t.me/protestinginfo/87
- Java для начинающих - YouTube
Платно:
- курс Баранцева ( в 2016 году это была отличная инвестиция, сейчас не знаю) - Программирование на Java для тестировщиков
- Javarush - Курсы Java для начинающих
- еще прошла два тренинга по Java внутри компании
- видео на YouTube - Java для начинающих, Сергей Немчинов
Все это мне помогло устроиться автоматизатором на Java. (Давно это было -период 2016-2018 год)
Плюс рекомендации дополнительно:
- Школа инженеров по автоматизации тестирования на Java - платно
- Основы языка Java - бесплатно
- Основы Java для автоматизации тестирования - бесплатно
———-
Python -то, что сама использовала при изучении.
Бесплатно
- Поколение Python: курс для начинающих
- Python: основы и применение
- Автоматизация тестирования с помощью Python и Selenium
Платно
- Python от Егора Векслера
- Автоматизация тестирования REST API (Starter)
- Автоматизация тестирования REST и gRPC API
- Тестирование ПО: Автоматизация и Программирование на Python: АPI
- Udemy - https://www.udemy.com/course/bestpython/
————
Еще хочу порекомендовать прочитать и сохранить:
Чек-лист перехода в автоматизацию (есть полезные ссылки и посты по Java в конце чек-листа)
———
А еще в комментариях укажу скрин чатов и Каналов, которые читаю и смотрю по Python
Очень приятно кстати попасть в подборку)
Так ли нужно уметь автоматизировать?
Мы все знаем, что ручное тестирование — это искусство, у меня жена ручной тестировщик и то насколько она погружена в продукт меня поражает.
На текущий момент ни одна нейросеть не сможет так быстро въехать в бизнес функционал, и предложить такой объем действительно важных и валидных сценариев.
А имея просто огромный опыт в проведении собеседовании, я знаю что и ни каждый человек на это способен и хороший ручной тестировщик на вес золота.
Однако, мир IT не стоит на месте, и рынок диктует свои условия. Автоматизация тестирования нам мой взгляд становится стандартом отрасли. В условиях постоянных изменений, скорость и эффективность разработки программного обеспечения становятся ключевыми факторами успеха. Как бы нам ни хотелось это изменить, факты говорят сами за себя: автоматизация — это не просто тренд, а скорее уже необходимость.
Зачем изучать автоматизацию?
1. Рынок требует, компании ищут специалистов, которые могут не только качественно тестировать вручную, но и автоматизировать процессы. Такие уникальные фуллстек спецы, как никогда, востребованы.
2. Знание автоматизации, при условии, что вы хороший ручник дополнит ваши навыки и откроет новые горизонты в карьере. Автоматизаторы, которые просто «перебивают» тест-кейсы в код, тоже особо не нужны. Нужны специалисты, которые понимают и тестируемый процесс, и технические аспекты его автоматизации.
3. Чем раньше вы начнете изучать автоматизацию, тем больше у вас возможностей в будущем. Даже зная хорошо один из популярных языков программирования, вам откроются вакансии с ЯП отличающимся от вашего, например со знанием Python, очень хорошо нанимают на вакансии где нужно переучиться на Golang. Естественно умение прогать даст вам конкурентное преимущество на рынке и сделает вашу кандидатуру более привлекательной для работодателей.
4. Ну и последнее это очень интересно и на это подсаживаешься как на наркотик, как только понимаешь, что иногда лучше потратить несколько часов на автоматизацию и сэкономить несколько дней, потом ищешь всегда возможности автоматизировать самые рутинные и бесячие процессы.
Но не стоит забывать, что тесты тоже придется поддерживать и они имеют свойство ломаться очень часто)))
Но это уже совсем другая история)
+3
Отзывы на курс
Автоматизация тестирования REST API (Starter)
Больше отзывов на мое обучение тут: @it_wizard_feedback
Новый отзыв на обучение, прикольно когда результат прям измеряется оффером)
Неочевидный блок else.
Давайте немного разбавим посты про архитектуру просто приколюшками, которые некоторые не знают.
В этом посте речь пойдет про неочевидный блок else, о котором я узнал из книжки, ее название я уже не помню )))
Но обычно на курсах про него не говорят.
Так вот это блок else в в цикле for.
Вы спросите нафига он там нужен? А я сейчас расскажу. Представим, что при создании сущности у нас есть определенный временной лаг,
такую ситуацию мы рассматриваем с учениками на курсе.
Есть следующий сценарий:
1. Отправляем запрос на создание пользователя
2. Сообщение попадает в rabbitMQ (очередь)
3. Очередь обрабатывается, создается активационный токен, который приходит письмом на почту
4. Активируем пользователя
На шагах 2-3 возникает временной лаг, поэтому может возникнуть ситуация, когда мы в автотесте пытаемся прочитать письмо, которое еще не пришло, тем самым мы получим ошибку.
Чтобы этого избежать, мы можем реализовать явные ожидания следующим образом:
def wait_for_email(user_name):
for _ in range(10):
response = requests.get('https://mail-server.com/emails')
# проверяем, что появилось письмо для нужного пользователя
for email in response.json():
if user_name in email['body']:
return email
# если письма не нашли то ждем еще 1 секунду и повторяем цикл
time.sleep(1)
else:
# если в течение 10 секунд ничего не появилось, то бросаем исключение
raise TimeoutError('Не смогли получить письмо в течение 10 секунд!')
# Создаем нового пользователя
requests.post('https://example.com/account', json={"username": "username", "password": "password"})
# ждем пока появится письмо в течение 10 попыток
email = wait_for_email('username')
# какие-то дальнейшие действия активация пользователя и тп.
Таким образом, мы можем выяснить, что письмо появилось в течение 10 секунд, и если это не так, то бросаем исключение.
Можно ли это переписать по другому?
Да можно например так:
def wait_for_email(user_name):
attempt = 0
while True:
response = requests.get('https://mail-server.com/emails')
for email in response.json():
if user_name in email['body']:
return email
if attempt == 10:
# если в течение 10 секунд ничего не появилось, то бросаем исключение
raise TimeoutError('Не смогли получить письмо в течение 10 секунд!')
# если письма не нашли то ждем еще 1 секунду и повторяем цикл
time.sleep(1)
attempt += 1
Или так, но такой код имеет смысл только если после выброса исключений нет других инструкций.
def wait_for_email(user_name):
while attempt := 0 <= 10:
response = requests.get('https://mail-server.com/emails')
for email in response.json():
if user_name in email['body']:
return email
attempt += 1
time.sleep(1)
# если в течение 10 секунд ничего не появилось, то бросаем исключение
raise TimeoutError('Не смогли получить письмо в течение 10 секунд!')
# то, что будет написано здесь никогда не выполнится
Вы можете писать любым из предложенных способов, но мне нравится использовать первый, на мой взгляд он самый легкочитаемый и понятный.
А вы знали про блок else в цикле for?
да - 👍 нет - 🔥Способы проектирования архитектуры API тестов.
ЧАСТЬ 2. Helper Pattern
В предыдущей части, мы рассматривали условный Handler Pattern, который должен был решить проблемы паттерна о котором мы будем говорить в этой части.
Повторюсь в рамках постов про архитектуру, названия паттернам я придумал самостоятельно, не отрицаю, что где-то на просторах интернета есть более правильные названия.
Так вот, в этом подходе мы выносим необходимую подготовку в классы помощники, тем самым появляется удобный способ переиспользования ранее написанных методов.
Например:
Сначала реализуются классы для необходимых API клиентов.
# first_client.py
class FirstApiClient:
def some_post_handler(self, **kwargs) -> FirstObject:
# Логика отправки запроса
return FirstObject()
def some_put_handler(self, **kwargs) -> PutObject:
# Логика отправки запроса
return PutObject()
# second_client.py
class SecondApiClient:
def some_get_handler(self, **kwargs) -> Any:
# Логика отправки запроса
return SecondObject()
Дальше пишем класс помощник, в котором реализуются методы упрощающие интерфейс работы с API клиентами, или предоставляющие более простой интерфейс объединяющий несколько шагов.
# helper.py
class SomeHelper:
def __init__(self, first_api_client, second_api_client):
self.first_api_client = first_api_client
self.second_api_client = second_api_client
def prepare_data(self) -> SomeValue:
first_object = self.first_api_client.some_post_handler(**kwargs)
second_object =self.second_api_client.some_get_handler(**kwargs)
return SomeValue(**first_object, **second_object)
Теперь производим инициализацию клиентов, и класса помощника где фикстуры являются довольно удобным способом.
python
# conftest.py
@pytest.fixture
def first_api_client() -> FirstApiClient:
return FirstApiClient()
@pytest.fixture
def second_api_client() -> SecondApiClient:
return SecondApiClient()
@pytest.fixture
def helper(first_api_client, second_api_client) -> SomeHelper:
return SomeHelper(first_api_client, second_api_client)
Сам тест у нас выглядит так.
python
# test_some_put_handler.py
def test_some_put_handler(helper):
data = helper.prepare_data()
response = helper.first_api_client.some_put_handler(**data)
assert response.json()["value"] == "some_value"
Плюсы:
1. Удобное переиспользование функций, достаточно использовать необходимый класс помощник
2. Удобное масштабирование, мы можем делать классы помощники в зависимости от доменной области например UserHelper, ProductHelper, OrderHelper и т.д.
3. Удобная сборка, класс помощник можно собирать, как конструктор передавая нужные клиенты
4. Лаконичные и читаемые тесты, вся логика инкапсулируется внутри классов помощников и оберток
5. Модульность, понятность и прозрачность кода и архитектуры
Минусы:
1. На большом проекте классы помощники могут разрастаться до нескольких тысяч строк кода
2. Паттерн порождает большое количество фикстур (Но эта проблема решаема)
Исходя из количества плюсов, которые я написал наверняка можно понять, что это мой любимый паттерн и его я использую на проектах.
Пишите минусы которые я не написал в тредик, буду благодарен и добавлю в пост)Напомню, что у меня есть бесплатные видео, которые я записывал некоторое время назад, если вы совсем нулевые имеет смысл посмотреть.
Естественно, тем кто уже знает базу, в этом необходимости нет, хотя иногда некоторые находят там что-то интересное)
Способы построения тестовой архитектуры API тестов.
ЧАСТЬ 1. Handler Pattern
Если с UI тестами, все плюс минус понятно, самый частоиспользуемый это PageObject.
То архитектуру построения API тестов я встречал самую разную.
Расскажу некоторые подходы к построению архитектуры тестов, которые встречал.
Скажу сразу, что названия этим подходам я присвоил самостоятельно.
Handler Pattern.
Суть заключается в том, что вспомогательные методы, которые нужны для конкретного теста хранятся внутри класса тестируемого метода.
Вот упрощенный пример:
# handler_name_logic.py
from modules.api_name.handler_name import HandlerName
from modules.api_name.another_handler_name import AnotherHandlerName
from modules.api_name.models import RequestModel
class HandlerNameLogic:
def __init__(self):
self.request = HandlerName
self.model = RequestModel
self.another_request = AnotherHandlerName
self.another_model = AnotherRequestModel
def simple_request(self, **kwargs):
return self.request(self.model(**kwargs))
def prepare_data(self):
return self.request.another_request(self.another_model("some value"))
@staticmethod
def check_result(actual_result, expected_result):
assert actual_result == expected_result, "Something went wrong"
# test_handler_name.py
from handler_name_logic import HandlerNameLogic
handler = HandlerNameLogic()
def test_handler_name():
prepared_data = handler.prepare_data()
actual_result = handler.simple_request(**prepared_data)
expected_result = "some value"
handler.check_result(actual_result, expected_result)
Плюсы такого подхода:
1. При большом количестве тестов, все побито на маленькие модули в которых легче разобраться
2. Вся вспомогательная логика лежит рядом, это помогает не скакать по всему проекту и разным модулям.
3. Хорошо смотрится в смоук тестах без сложных интеграционных сценариев, где идет взамодействие нескольких ручек.
Минусы:
1. Непонятно где писать код, который переиспользуется, с одной стороны вроде бы в классе логики хэндлера, но из этого возникает следующая проблема.
2. Неочевидно откуда делать импорты уже реализованных функций, потому, что они размазаны по классам логики, не зная проекта скорее всего возникнет дублирование кода.
3. Сложная архитектура построения фреймворка, вручную поддерживать такой проект сложно, возникает много обвязок и абстракций для конвертации "метода в класс"
4. Если общие методы выносить в общие классы помощники, плюсы такого подхода смываются, потому, что логика размазывается еще и на классы помощники.
Я рассказал первый из подходов, мне он не очень нравится, но вы можете попробовать обкатать у себя на проекте и оценить.Неочевидный блок else, в блоке try-except.
Я встречал такие вопросы на собеседовании, когда просили написать полную конструкцию try-except.
Довольно часто про него забывают или не знают, поэтому хочу про него рассказать.
Что это за блок и как его использовать?
Например:
try:
connection = DBConnection()
except ConnectionError as e:
...
else:
dataset = connection.execute("SELECT * FROM table")
finally:
connection.close()
В данном случае блок else выполнится, только если пройдет успешное выполнение try, иначе будет выполнен блок except и в качестве финального блока будет выполнен finally, который закроет соединение.
Можно ли это написать по другому?
Да можно, например, так:
try:
connection = DBConnection()
dataset = connection.execute("SELECT * FROM table")
except ConnectionError as e:
...
finally:
connection.close()
Но в данному случае, область перехвата ошибки расширяется и на сам запрос, нужно это или нет зависит от ситуации. В любом случае это расширяет область локализации потенциальных ошибок.
Еще можно написать так:
try:
connection = DBConnection()
except ConnectionError as e:
...
dataset = connection.execute("SELECT * FROM table")
connection.close()
Тоже имеет место быть, но успешное закрытие соединения произойдет только если успешно выполнится запрос, что так же накладывает свои риски.
Мне нравится использовать этот блок, на мой взгляд, он делает код более читаемым и прозрачным.
А вы знали про этот блок?
да - 👍 нет - 🔥Задача с собеседования:
Поиск индекса сбалансированного массива
У меня есть друг, который регулярно ходит на собеседования. Естественно, мне всегда интересно, какие задачки там появляются, и потом я стараюсь решить их самостоятельно без помощи ChatGPT.
Большинство задач не слишком сложные и не требуют, чтобы акцентировать на них внимание, но некоторые интересные я отмечаю.
Например:
# Дано: Массив int
# Результат: Индекс - сумма чисел слева = сумме чисел справа
# Если не существует - вернуть -1
#
# Input: nums = [1, 7, 3, 6, 5, 6]
# Output: 3
То есть, число с индексом 3 (это 6) делит список на две части, суммы которых равны, 1 + 7 + 3 = 11 и 5 + 6 = 11, а 6 делит их пополам.
То есть, ещё раз: цель — найти индекс числа, которое делит список на две части, если сумма чисел этих двух частей равна. Если такого числа нет, то нужно выводить -1, а если число в списке равно -1, то следует выбрасывать исключение.
Скажу честно, я не сразу вкурил, что от меня требуется, и поэтому приступил к задаче только после того, как понял её условия.
Первая мысль, которая пришла мне в голову, — это одновременно итерироваться с разных концов списка, но это выглядело как полная дичь, и я начал думать дальше.
В итоге я решил использовать срезы (slice), и я буду делать срезы по индексу элемента в списке. Тем самым я буду делить список на две части и считать сумму элементов этих частей: от 0 до i — это первая часть, а от i + 1 до конца списка — вторая часть.
Чтобы предотвратить преждевременный выход из цикла и возвращать -1 только после того, как мы проверим все элементы списка, я решил создать переменную index_flag, которая по умолчанию равна -1 и изменяется только если наша цель по поиску индекса выполнена.
Итоговый код получился таким:
def search_index(list_):
index_flag = -1
for i in range(len(list_)):
if i < 0:
raise ValueError("Число в списке должно быть больше или равно 0")
left_list = sum(list_[:i])
right_list = sum(list_[i + 1:])
if left_list == right_list:
index_flag = i
return index_flag
print(search_index(list_))
Ну и конечно мне было интересно было узнать, какое решение предложит ChatGPT.
Вот его ответы:
def find_balance_index(arr):
total_sum = sum(arr)
left_sum = 0
for i in range(len(arr)):
if left_sum == total_sum - arr[i]:
return i
left_sum += arr[i]
total_sum -= arr[i]
return -1 # Если индекс не найден
arr = [1, 7, 3, 6, 5, 6]
balance_index = find_balance_index(arr)
print("Индекс сбалансированного массива:", balance_index)
и такое:
def find_balance_index(arr):
total_sum = sum(arr)
if any(num <= 0 for num in arr):
raise ValueError("Массив должен содержать только положительные числа.")
for i, num in enumerate(arr):
total_sum -= num
if total_sum == 0:
return i
return -1
arr = [1, 7, 3, 6, 5, 6]
try:
result = find_balance_index(arr)
print("Индекс сбалансированного массива:", result)
except ValueError as e:
print("Ошибка:", e)
Мое решение мне кажется гораздо понятнее, но тут вы уже решайте сами.
В любом случае, решение таких задачек неплохо развивает алгоритмическое мышление.
А какие задачки на собесах встречали вы?Как использовать try-except?
Как вы знаете я обучаю автоматизации тестирования и в одном из домашних заданий ученик использовал такую конструкцию:
def test_register_new_user(db, api, login, email):
# Здесь какая то логика до
try:
dataset_delete = db.delete_user_by_login(login=login)
api.mailhog.delete_message_by_login(login=login)
assert len(dataset_delete) == 0
except:
pass
api.account.register_new_user(
login=login,
email=email
)
# Здесь какая то логика после
Что плохо в этом коде?
- Первое, что бросается в глаза - это использование просто оператора except без уточнений, в таком случае в данном блоке кода, мы будем перехватывать любые исключения, которые возникли в процессе его выполнения, будь это падение подключения к базе данных или то, что не смогли удалить сообщение нужного юзера из mailhog.
- Второе, использование оператора assert внутри try-except с не уточненным названием исключения, то есть даже если он и сработает мы его перехватим и тест пойдет дальше и наш assert в таком случае бесполезен.
К чему это может привести:
- Ложно положительный результат выполнения теста, потому, что ошибки, которые возникли мы их просто игнорировали
- Тяжелая локализация причины падения теста во время его выполнения, по той же причине, что все ошибки мы игнорировали и без тщательного анализа логов(если такие есть) мы не узнаем какие именно ошибки произошли.
Как сделать более правильно?
- Во первых нужно использовать try-except с уточненным названием исключения
- Во вторых оборачивать не весь блок кода, а максимально атомарную функцию в которой мы ожидаем исключение
- В третьих вынести блок try-except из теста
- В четвертых убрать assert если нам важно, чтобы тест прошел успешно, независимо от того удалось удалить сообщение или нет
# db.py
def delete_user_by_login(self, login):
# метод удаления юзера в классе базы данных
try:
return self.execute(f"DELETE FROM users WHERE login = '{login}'")
except TypeError as e:
log.message(f'В базе данных не найден пользователь с логином {login} {e}')
return []
# test_user.py
def test_register_new_user(db, api, login, email):
# Здесь какая то логика до
dataset_delete = db.delete_user_by_login(login=login)
api.mailhog.delete_message_by_login(login=login)
api.account.register_new_user(
login=login,
email=email
)
# Здесь какая то логика послеЯ хотел бы поделиться с вами некоторыми языковыми конструкциями, которые помогут сократить ваш код и сделать его более читаемым.
В этом посте я расскажу о нескольких приемах, таких как:
- Избежание использования оператора else.
- Применение тернарных операторов.
- Массовое присвоение значений.
Эти простые изменения могут улучшить вашу кодовую базу! Давайте рассмотрим примеры.
1. Не использовать оператор else
Например, вместо использования оператора else в некоторых случая его можно просто игнорировать:
def foo(param):
if param == 1:
return 1
else:
return 2
Данную функцию можно написать так:
def foo(param1):
if param == 1:
return 1
return 2
Логика функции в данном случае не меняется, но сокращается код, при этом не ухудшается его читаемость.
2. Тернарные операторы
Например вместо блока условных конструкций можно использовать тернарный оператор:
value = None
if param == 1:
value = 1
else:
value = 2
с использованием тернарного оператора наша конструкция сократится до одной строчки:
value = 1 if param == 1 else 2Есть еще тернарный оператор or, который тоже помогает сократить код в некоторых случаях:
dataset = (1, 2, 3, 4)
value = dataset if dataset else "Данные из базы данных не были получены"
указанный пример с использованием оператора or можно написать так:
dataset = (1, 2, 3, 4)
value = dataset or "Данные из базы данных не были получены"
! Есть еще один интересный способ применения тернарного оператора — вместе с кортежами !
is_raining = True
weather = ("sunny", "rainy")[is_raining]
print("Today's weather is", weather)
Значения True и False конвертируются в 1 и 0, и соответственно выбираются значения из кортежа по этим индексам.
3. Массовое присвоение значений
Несмотря на то, что такой способ более читаемый и чистый:
a = 1
b = 2
В некоторых случаях можно использовать массовое присвоение значений, например:
a, b = 1, 2
Есть и другие способы использования сокращения и оптимизации кода, я расскажу о них если вспомню =))Как тестировать SQL-функции и как в будущем это автоматизировать?
Такой вопрос мне переадресовала Надя.
Я работал в финтехе на легаси-проекте, где вся логика была построена на хранимых процедурах. Самый большой минус в том, что тестирование на этом уровне не развито в отрасли и представляет довольно часто ряд костылей, одним из которых я поделюсь. Я пробовал писать тесты прямо на синтаксисе SQL, где результаты просто выводились обычными принтами True или False (прошел/не прошел). Это было интересно, но помогало только решению конкретной задачи, и данный подход был нерасширяемым.
Ну ладно, хватит слов, давайте расскажу свой костыль. На мой взгляд, удобнее всего это сделать на Python, на примере MS SQL Server.
Погнали!
С помощью pyodbc сначала я пишу простой клиент, который будет логгировать каждый запрос и преобразовывать его в список словарей для удобства работы с данными по колонкам.
import pyodbc
import os
import structlog
class BaseClient:
def __init__(self, server, database, user, password, autocommit=False, logging_level=logging.DEBUG):
self.log = structlog.get_logger(self.__class__.__name__).bind(service='db')
system_name = os.name
if system_name == 'posix':
driver = 'ODBC Driver 17 for SQL Server'
else:
driver = 'SQL Server'
connection_string = f'DRIVER={{{driver}}};' \
f'SERVER={server};' \
f'DATABASE={database};' \
f'UID={user};' \
f'PWD={password};'\
f'Trusted_Connection={trusted_connection}'
with pyodbc.connect(connection_string, autocommit=self.autocommit, timeout=30) as connect:
self.connect = connect
self.cursor = connect.cursor()
def logged_query(self, query):
log = self.log.bind(request_id=str(uuid.uuid4()))
print(f'\n{query}')
log.msg(
'request',
caller=inspect.stack()[1][3],
func_name=inspect.stack()[0][3]
)
result = self._to_dict(self.cursor.execute(query))
if result:
log.msg(
'response',
result=result,
)
return result
@staticmethod
def _to_dict(cursor):
try:
columns = [column[0] for column in cursor.description]
except TypeError:
return []
else:
dataset = [dict(zip(columns, row)) for row in cursor.fetchall()]
return dataset
После того как базовый клиент написан, можно уже реализовать класс с вызовами хранимых процедур. Можно отнаследовать его от базового класса, чтобы использовать его методы и реализовать вызов.
class ApiTestProcedures(BaseClient):
def some_procedure_execute(self, param1, param2):
"""
SET NOCOUNT ON - это писать обязательно, иначе не будет работать))
"""
query = f"""
SET NOCOUNT ON;
EXEC [test].[SomeProcedure]
@Parameter1 = {param1}
,@Parameter2 = {param2}"""
postings = self.logged_query(query)
return postings
Вся подготовка сделана теперь можно писать тест, тут уже как обычно.
@pytest.fixture(scope="session")
def db_client():
client = ApiTestProcedures("тут параметры подключения")
yield client
client.close()
def test_some_procedure(db_client):
result = db_client.some_procedure_execute(param1=1, param2=2)
for row in result:
assert row["column_name"] == "column_value"
Вы можете использовать другие библиотеки для сериализации данных, например, SQLAlchemy или Pandas для удобства работы и анализа данных. Для этого нужно вместо метода _to_dict реализовать свои функции упаковки данных.
Пишите вопросы в тред, если возникнут интересные темы, постараюсь ответить в новом посте!
Endi mavjud! Telegram Tadqiqoti 2025 — yilning asosiy insaytlari 
