Валерий | AQA Engineer | Автотестирование на Python | REST, gRPC, GraphQL
رفتن به کانال در Telegram
Сделаю из тебя крутого AQA инженера на Python. • Преподаю лучшие тренинги по автоматизации тестирования API • Senior Python developer | AQA lead, 7 лет в IT
نمایش بیشتر1 511
مشترکین
-124 ساعت
+57 روز
+830 روز
آرشیو پست ها
Repost from Artsiom Rusau QA Life - Тестировщик с нуля
Скам при найме. Будьте осторожны. Поделилась одна из студенток. Это уже не первый случай, в чате моего бесплатного курса тоже часто пишут про похожие истории.
Артем, привет! Наткнулась на скамщиков на хх.ру, перенаправляют на телеграмм канал, их блокируют на хх, но не своевременно. Ищут начинающих тестировщиков, представляясь под видом разных крупных компаний. На данный момент это Коде. Назначают и проводят собеседования довольно неплохо для мошенников, основная их задача заставить человека выйти из своего эпл айди и войти в «коммерческий» для тестирования приложений и заведения баг репортов, после этого блокируют телефон и требуют выкуп. Аккаунты до сих пор рабочие, трудятся не покладая рук. Хотелось бы предостеречь ребят, кто начинает поиски и нервничает перед собеседованием обратить на это внимание. Если у тебя есть возможность распространить эту информацию буду благодарна, возможно кому-то пригодится Скрин аккаунта Hr прикрепила
Статическая типизация в Python — когда уткам уже не доверяешь 🧊
Python — язык с динамической природой.
Но что, если хочется надёжности уже до запуска?
Добро пожаловать в мир статической типизации.
Без уток, без гусей — просто строгая проверка типов.
🧐 Что это вообще?
Статическая типизация — это когда ты явно указываешь типы переменных, аргументов и возвращаемых значений, а специальные инструменты (например, mypy) проверяют это до выполнения кода.
def greet(name: str) -> str:
return f"Привет, {name}"
greet("Питон") # Всё хорошо
greet(123) # mypy тут же даст ошибку
Python по-прежнему выполнит код, даже если ты передашь int, но анализатор заранее скажет: «что-то не так».
На этом примере особо не почувствуешь плюсов. Но если ты работаешь с большим количеством DTO, например pydantic модели, запросов и ответов или у тебя большой проект, когда ты не знаешь ожидаемых типов становится очень тяжело программировать. Среда разработки подсказывает хуже.
В общем сейчас я рекомендую всем по возможности проставлять аннотации.
📌 Как это работает?
• Используется синтаксис аннотаций (x: int, -> str)
• Проверка идёт снаружи, через инструменты (mypy, pyright, IDE)
• Ошибки ловятся ещё до запуска (в идеале — на этапе CI)
⚖️ Зачем это нужно?
• Уменьшить баги, так как python при запуске не выбрасывает ошибки при расхождении типов.
• Упростить навигацию по коду (IDE покажет сигнатуры, автокомплит и т.п.)
• Улучшить читаемость: ты сразу видишь, что принимает и возвращает функция
• Повысить доверие к чужому коду: «ничего не сломаю, если передам вот это»
На ступени Advanced мы пишем код практически без аннотаций, но на Professional мы уже используем статическую типизацию и инструменты для анализа нашего кода.
🔥 Советы:
• Начинай с простого: добавляй типы в публичные функции
• Используй mypy — он легко интегрируется в проекты
• Не бойся писать Optional, Union, TypedDict, Literal — они реально спасают
✔️ Коротко:
• Python остаётся динамическим, но может быть типизированным
• Статическая типизация помогает ловить ошибки до запуска
• Ты не обязан, но ты можешь — и это делает код сильнее
🎯 Итог:
Утиная типизация даёт свободу.
Гусиная — структуру.
Статическая — уверенность.
А вместе они дают тебе гибкий, читаемый и защищённый код.
Про модуль typing и type hints можно почитать в официальной доке.
——————————-
📱 TG-сообщество
📱 Обучение
📱 ОтзывыВпереди выходные, почему бы не уделить это время тюнингу своей гитхаб аккаунта)
💻 Как оформить GitHub-профиль, чтобы он работал на вас.
Если вы пишете код, GitHub — это не просто хранилище кода. Это ваша публичная визитка. Грамотно оформленный профиль помогает:
🔹 Привлечь внимание рекрутеров и заказчиков
🔹 Выделиться среди других кандидатов
🔹 Показывает, что вы заботитесь о деталях и уважаете своих коллег по цеху
🔹 Да и в целом даже самому приятно смотреть на красивый профиль.
📌 Что можно сделать:
– Создать README для профиля с краткой информацией о себе
– Выделить ключевые проекты и красиво их оформить
– Добавить бейджи, статистику и немного стиля
– Подчеркнуть свою активность, навыки и интересы
Сделать это довольно просто, достаточно просто создать репозиторий с названием своего GH профиля и положить туда README.
👀 Нашёл пару отличных гайдов, которые помогут навести порядок и сделать профиль действительно заметным:
📄 Как информативно оформить профиль на GitHub?
📄 Оформляем README-файл профиля на GitHub
Но сильно не увлекайтесь, слишком много свистелок перделок тоже не есть хорошо))
Если давно хотели прокачать свой профиль, но откладывали — самое время ✌️
А если уже что-то сделали — кидайте ссылки, интересно посмотреть!
——————————-
📱 TG-сообщество
📱 Обучение
📱 Отзывы
Статическая утиная типизация — когда нужно понять, что это утка, ещё до запуска.
Мы уже с вами узнали:
• Утиная типизация — “если выглядит как утка и крякает как утка — это утка”
• Гусиная типизация — “эта утка официально записана в реестре как утка” (через ABC)
Теперь познакомимся с третьим подходом — статической утиной типизацией через Protocol.
Что это такое❓
Protocol из модуля typing позволяет описать интерфейс по ожидаемому поведению — без наследования.
Если у объекта есть нужные методы — он считается совместимым.
from typing import Protocol
class Writer(Protocol):
def write(self, data: str) -> None: ...
class FileWriter:
def write(self, data: str) -> None:
print(f"Пишу: {data}")
def save(w: Writer, msg: str):
w.write(msg)
save(FileWriter(), "Привет!")
Обрати внимание: FileWriter не наследует Writer, но типовые анализаторы (mypy, pyright) поймут — всё ок.
Потому что поведение совпадает.
Зачем это нужно❓
• Статическая проверка интерфейсов до запуска
• Без isinstance() и жёсткой иерархии
• Отлично подходит для тестов, моков
• Совместим с mypy, Pyright, Pylance и другими инструментами
• С полной свободой: хочешь — наследуй, хочешь — просто реализуй нужные методы
Утиная типизация была про “смотрим, что умеет объект”.
Статическая утиная — про “давайте это ещё и проверим заранее”.
✔️ Коротко:
• Protocol — это интерфейс без наследования
• Работает по принципу “если поведение совпадает — значит, подходит”
• Используется для статической проверки типов
• Совместим с mypy, Pyright и другими анализаторами
Можем ли мы проверить, что утка это утка используя протокол в рантайме?
Да например так:
from typing import Protocol, runtime_checkable
@runtime_checkable
class Writer(Protocol):
def write(self, data: str) -> None: ...
class FileWriter:
def write(self, data: str) -> None:
print("Пишу:", data)
fw = FileWriter()
print(isinstance(fw, Writer)) # True
🧠 Статическая утиная типизация помогает писать гибкий, но при этом безопасный код.
Это некий баланс между свободой и контролем.
Более подробно про протоколы можно почитать тут.
——————————-
📱 TG-сообщество
📱 Обучение
📱 ОтзывыГусиная типизация — когда утки 🦆 недостаточно
(а интерфейс всё-таки нужен)
В Python есть утиная типизация — “если выглядит как утка и крякает как утка — это утка”.
Это удобно. Но не всегда безопасно.
Название «гусиная типизация» возникло как противопоставление «утиной»: если утиная типизация в Python ориентируется на поведение объекта (если у него есть нужные методы — значит, подходит), то гусиная делает акцент на происхождение — как в биологии, где гусей и уток раньше классифицировали по внешнему сходству, но позже стали объединять по общим предкам (кладистика). Аналогично в программировании: два класса могут иметь одинаковые методы (например, draw()), но с разным смыслом — и потому они не обязательно взаимозаменяемы. Гусиная типизация требует явного указания интерфейса через абстрактные базовые классы (ABC) — чтобы быть уверенным, что объекты действительно «родом» от нужного интерфейса, а не просто «выглядят похоже».Иногда нужно чётко сказать: “Я реализую этот интерфейс” — и получить защиту на уровне кода. Вот тут появляется гусиная типизация 🦢. Что это такое?
Гусиная типизация — это подход к проверке типов во время выполнения, основанный на использовании абстрактных базовых классов (ABC).Да, в Python нет ключевого слова interface, но есть модуль abc, который позволяет создать интерфейс через абстрактный класс. Пример:
from abc import ABC, abstractmethod
class MessageSender(ABC):
@abstractmethod
def send(self, message: str) -> None:
pass
class TelegramSender(MessageSender):
def send(self, message: str) -> None:
print(f"Отправлено в ТГ: {message}")
class EmailSender:
def send(self, message: str) -> None:
print(f"Письмо: {message}")
Теперь можно сделать проверку:
isinstance(TelegramSender(), MessageSender) # True
isinstance(EmailSender(), MessageSender) # False
Хотя EmailSender реализует метод send, он не унаследован от MessageSender.
Python этого не “засчитает” — и это отличие от утиной типизации.
❓Для чего это нужно?
- Когда важно явно указать, что ты реализуешь интерфейс
- Когда нужна статическая проверка типов (например, через mypy)
- Когда hasattr() уже не спасает и хочется чёткости и надёжности
✔ Коротко:
- Утиная типизация — поведение важнее наследования
- Гусиная типизация — поведение + явная декларация через ABC
- Поддерживается isinstance(), issubclass() и статическими анализаторами
В Python можно писать и гибко, и строго — выбор за тобой.
Понимание обеих моделей делает твой код читаемым, надёжным и масштабируемым.
В глоссарии Python есть статья посвященная абстрактным базовым классам.
——————————-
📱 TG-сообщество
📱 Обучение
📱 Отзывы“Если оно ходит как утка… 🦆” — питон не требует паспорта.
А вы знали, что Python поддерживает несколько видов типизации? 🤔
Например:
- 🦆 Утиная
- 🦢 Гусиная
- 📜 Статическая утиная типизация
- 🔍 Статическая типизация
В этом посте поговорим про ту типизацию, которая изначально была в Python — Утиную.
Одно время я все никак не мог понять, что это такое. Если вы уже в теме — отлично! Если нет — сейчас расскажу.
Python — язык про поведение, а не про формальности.
Здесь не так важно, от какого класса ты наследуешься.
Важно, что ты умеешь делать!
Пример:
Представь, тебе нужна "последовательность": список, массив, колода карт — не суть.
Python не будет проверять, наследуешься ли ты от `list` или `collections.abc.Sequence`.Он просто попробует вызвать: -
__len__()
- __getitem__()
Если получилось — значит, ты "последовательность". ✅
class FrenchDeck:
def __init__(self):
self._cards = []
def __len__(self):
return len(self._cards)
def __getitem__(self, pos):
return self._cards[pos]
🔎 Обратите внимание: этот класс ни от кого не наследуется!
Но его можно:
- перебирать в цикле,
- брать срезы,
- передавать в `sorted()`, `random.choice()` и т.д.
Потому что он ведёт себя как список!
То есть тебе просто нужно реализовать нужные методы — и этого достаточно.
🦆 Что такое утиная типизация?
“Если что-то ходит как утка, крякает как утка — значит, это утка.”
Такой подход даёт гибкость:
- Можно писать адаптивный код без лишней иерархии.
- Главное — понимать, какие "протоколы поведения" ждёт от тебя Python.
Хочешь разбираться в таких концепциях глубже?
Учись читать поведение, а не только сигнатуры!
——————————-
📱 TG-сообщество
📱 Обучение
📱 ОтзывыВсем привет!
Поздравляю с прошедшими майскими праздниками, но.
Майские - закончились. Время включаться.
Шашлыки -были. Отдых - был.
Теперь - время расти как инженер.
Сегодня стартует новый поток курса по API-автотестированию.
И это последний день, когда можно впрыгнуть в этот поток.
После этого набор закрываю.
Если ты хочешь:
- разбираться в архитектуре
- писать уверенный продакшн-код
- не просто “ковыряться в Postman”, а строить фреймворки
Переходи по ссылке и выбирай нужный модуль.
Успей сегодня.
Работаем.
——————————-
📱 TG-сообщество
📱 Обучение
📱 Отзывы
В этом канале мы с вами рассматриваем преимущественно темы автоматизации тестирования API.
У меня все никак не доходили руки рассказать про инструмент, который вы возможно даже использовали и который занимается fuzzing тестированием.
🔍 Что такое фаззинг-тестирование?
Фаззинг (fuzzing) — это метод автоматического тестирования, при котором программа проверяется на уязвимости и ошибки с помощью передачи случайных или полу-случайных данных на вход. Цель — найти краш, утечку памяти, неожиданное поведение или уязвимости безопасности.
🧩 Виды фаззинга:
- Мутационный фаззинг — изменяет существующие входные данные (например, искажая корректный JSON).
- Генеративный фаззинг — создает данные с нуля на основе спецификации (например, OpenAPI/Swagger).
А теперь собственно про сам инструмент.
⚡️ Schemathesis — фаззер для API
Schemathesis — это инструмент для тестирования API, основанный на OpenAPI/Swagger-схеме сервиса. Он автоматически генерирует тестовые сценарии и "обстреливает" ваш API некорректными или неожиданными данными, чтобы выявить:
- Ошибки сервера (500 Internal Server Error)
- Уязвимости (SQL-инъекции, XSS)
- Проблемы валидации
- Неожиданное поведение
🚀 Как работает?
1. Берет схему OpenAPI/Swagger.
2. Генерирует корректные и некорректные запросы на основе property based testing.
3. Отправляет их в API и анализирует ответы.
🔧 Пример использования:
pip install schemathesis
schemathesis run https://example.com/openapi.json
Или с кастомными параметрами:
schemathesis run --checks all --max-response-time 5000 http://api.example.com/schema.json💡 Зачем это нужно? - Находить баги до продакшена. - Улучшать отказоустойчивость API. - Автоматизировать тестирование безопасности. На практике имеет смысл использовать скорее если вы вылизываете свое API и у вас есть ресурсы, чтобы упороться, кривые схемы будут порождать огромное количество ошибок которые с высокой долей вероятности никто не будет править. Инструмент безумно простой в использовании и очень эффективный, в целом можно внедрить отдельной не блокирующей гитлаб джобой с заделом на будущее. Кстати недавно обнаружил, что он еще умеет в GraphQL Думаю добавлю урок с этим инструментом в курс Advanced, лишним не будет) ——————————- 📱 TG-сообщество 📱 Обучение 📱 Отзывы
“Ты не хуже, просто у тебя не было нужного тренера.”
Недавно мой ученик написал:
«К нам пришёл человек с 4 годами опыта в автотестах — и он знает ровно столько же, сколько я.»
Он добавил:
«я хотел сказал, что с таким учителем как ты я развил потенциал и опыт меньше чем за год.»
Это не про меня. Это про силу правильного подхода.
Про то, как практика, структура, и обучение дают больше, чем 4 года «просто работы».
Что даёт результат быстрее опыта в вакууме?
• Фокус не на инструментах, а на понимании архитектуры
• Практика, приближённая к реальным задачам на проекте
• Преподаватель, который объясняет
Скоро старт нового потока.
Если ты:
• Хочешь не просто «писать тесты», а строить фреймворки
• Чувствуешь, что время идёт, а прогресса нет
• Или уже в тестировании, но хочешь расти как инженер —
присоединяйся.
Ты не отстаёшь. У тебя просто ещё не было правильной траектории.
——————————-
📱 TG-сообщество
📱 Обучение
📱 Отзывы
Отзыв на курс Advanced, запоздалый немного, но вот только созрел)Когда отзыв приходит не сразу — это тоже хорошо. Значит, человек не просто посмотрел курс, а пожил с ним. Вот кусочек одного такого отзыва на курс Advanced:
«Сейчас пробую свои собственные проекты писать по этой же структуре и все реально легко работает, прописал основные моменты и дальше остается только описывать апишку и шаги тестов, все работает красиво с первых дней)».✔️ Вот за это я и люблю поздние фидбэки. Курс отлежался в голове, прошёл через пару шишек, и вдруг всё встало на свои места. Читаемость, структура, простота — это не случайно. 🔥Вот ради этого и делался Advanced. Весь курс построен так, чтобы после него можно было не “переизобретать”, а спокойно писать API-тесты на понятной архитектуре, без боли и гадания, «как бы тут лучше». И самое приятное — когда люди возвращаются за следующей ступенью. Значит, не просто зашло, а стало частью рабочего процесса. Спасибо за отзыв — и жду на 🏆 про-уровне, когда придёт время. Полный отзыв можно прочитать тут. ——————————- 📱 TG-сообщество 📱 Обучение 📱 Отзывы
📌 D – Dependency Inversion Principle (DIP)
Принцип инверсии зависимостей
“Зависимости должны быть от абстракций, а не от конкретных реализаций.”
В DIP помогает строить гибкие, легко тестируемые и переиспользуемые компоненты.
Особенно когда нужно подменить авторизацию, логирование, сделать allure-разметку или сбор coverage
В чём суть?
Класс не должен напрямую зависеть от конкретной реализации, он должен зависеть от интерфейса (абстракции).
🧪 Пример: плохая реализация (без DIP)
Скажу честно, сам я тоже когда-то грешил завязываясь на конкретную реализацию клиента, в данном случае requests.Session()
class ApiClient:
def __init__(self):
self.session = requests.Session()
def get_user(
self,
user_id
) -> dict:
response = self.session.get(
f"{API_URL}/user/{user_id}"
)
return response.json()
Проблемы:
• Жёсткая зависимость от requests, если мы захотим использовать, например httpx, мы не сможем этого сделать без изменения кода.
• Нельзя замокать поведение в тесте
• Нарушен DIP: клиент зависит от деталей
✅ Как сделать правильно (с применением DIP)
То есть говорим какие методы обязательно должны быть у принимаемого на вход клиента.
В Python нет интерфейсов в классическом понимании, поэтому abc.ABC и @abstractmethod — это абстрактные классы, которые эмулируют интерфейсы.
1. Вводим интерфейсы:
from abc import ABC, abstractmethod
class BaseHttpClient(ABC):
@abstractmethod
def get(
self,
url: str,
**kwargs: Any
) -> dict:
pass
2. Реализация:
Наследуемся и описываем методы соблюдая контракт, "контракт" — это обязательство реализовать методы, объявленные в абстракции (BaseHttpClient).
При соблюдении этих правил, любой клиент, который соблюдает контракт, может быть безболезнено (в идеале) использован.
class HttpClient(BaseHttpClient):
def get(
self,
url: str,
**kwargs: Any
) -> dict:
return requests.get(url, **kwargs).json()
3. Внедряем зависимости:
То есть любой наследник BaseHttpClient не должен поломать код.
class ApiClient:
def __init__(
self,
http_client: BaseHttpClient
):
self.http_client = http_client
def get_user(self, user_id, headers) -> dict:
return self.http_client.get(
f"{API_URL}/user/{user_id}",
headers=headers,
).json()
🧪 Теперь в тесте:
# Мы можем или замокать клиент тем самым написать тест без реального апи
class MockHttpClient(BaseHttpClient):
def get(self, url, headers):
return {"id": 123, "name": "Test User"}
def test_get_user():
client = ApiClient(MockHttpClient())
user = client.get_user(123)
assert user["id"] == 123
# Можем добавить свою логику, например логгирование запроса
class LoggedHttpClient(BaseHttpClient):
def get(
self,
url,
headers
) -> dict:
print(url, headers)
response = self.http_client.get(
url,
headers=headers
)
print(response.text)
return response.json()
# И здесь тест уже будет с логами
def test_get_logged_user():
client = ApiClient(LoggedHttpClient())
user = client.get_user(123)
assert user["id"] == 123
Профит:
• Подменили зависимости без боли
• Не нужен реальный backend для тестов
• Код клиента не меняется — только зависимости
DIP особенно полезен когда:
• Пишешь интеграционные и контрактные тесты
• Нужно мокать внешние сервисы
• В проекте много обвязки (логов, ретраев, токенов) — и ты хочешь это всё отделить
• Требуется расширяемость (добавить кеш, валидацию, другой http-клиент)
Вывод
Применение DIP в API‑тестах:
🔹 Упрощает подмену зависимостей
🔹 Повышает читаемость и тестируемость
🔹 Позволяет изолировать бизнес‑логику от инфраструктуры
🔹 Снижает связанность компонентов
🔥Перешли другу\коллеге пусть тоже знает)
На этом мы закрываем тему SOLID. )
——————————-
📱 TG-сообщество
📱 Обучение
📱 Отзывы📌 I – Interface Segregation Principle (ISP)
Принцип разделения интерфейсов
“Не заставляй клиента зависеть от того, что он не использует.”
В автоматизации это часто встречается, когда в один «удобный» хелпер пихают вообще всё подряд — и API, и UI, и БД.
🧪 Пример
Представим, что кто-то решил упростить жизнь и создал “универсальный” интерфейс:
class TestHelper:
def get_sms_code_by_api(self): ...
def login_from_ui(self): ...
def check_user_created_in_db(self): ...
def get_kafka_message(self): ...
Теперь любой тест, независимо от контекста, тянет за собой весь этот набор.
• Тесты на API зависят от методов, которые они не используют
• UI‑тесты вынуждены тащить зависимости от kafka или БД
• Такие классы очень быстро увеличиваются в размерах, их становится тяжело поддерживать.
Нарушен ISP — мы создали жирный интерфейс, который знает всё и сразу.
✅ Как исправить?
Разделить интерфейсы по зонам ответственности:
class ApiHelper:
def get_sms_code(self): ...
class UiHelper:
def login(self): ...
class KafkalHelper:
def get_message(self): ...
class DbHelper:
def check_user_created(self): ...
Теперь каждый тест получает только то, что ему реально нужно:
def test_ui_login(ui: UiHelper):
ui.login()
def test_registration_in_db(db: DbHelper):
...
db.check_user_created()
Как правильно сделать удобный хэлпер, не нарушая принцип ISP?
class TestHelper:
def __init__(
self,
ui: UiHelper,
db: DbHelper,
kafka: KafkalHelper,
api: ApiHelper
) -> None:
self.ui = ui
self.kafka = kafka
self.db = db
self.api = api
Теперь TestHelper — это фасад, который объединяет несколько интерфейсов, но не нарушает ISP, потому что:
1. Клиенты (тесты) зависят только от того, что используют – если тест работает только с UI, он может обращаться только к helper.ui, не зная о других модулях.
2. Интерфейсы внутри фасада разделены – UiHelper, ApiHelper и другие остаются независимыми.
Теперь мы используем только то что нужно, но при этом не нарушаем ISP и тест выглядит так.
def test_user_registration(helper: TestHelper):
helper.ui.login()
helper.api.get_sms_code()
helper.kafka.get_message()
helper.db.check_user_created()
🔧 Когда мы соблюдаем ISP то:
• Проще вникнуть в небольшие независимые классы
• Проще переиспользовать компоненты
• Удобно если много интеграций API, UI, DB
Вывод:
Соблюдение ISP делает код:
🔹 Более модульным — только нужные зависимости
🔹 Легче тестировать
🔹 Проще поддерживать, меньше случайных поломок
🔥 Хочешь научиться писать код правильно?
Переходи по ссылке 📱 Обучение , я тебя научу, старт нового потока уже 12 мая)
——————————-
📱 TG-сообщество
📱 Обучение
📱 Отзывы📌 L – Liskov Substitution Principle (LSP)
Принцип подстановки Лисков
«Если в коде вместо базового класса подставить его наследника — поведение не должно ломаться.»
На самом деле было довольно сложно было придумать пример в контексте автотестирования, но давайте попробуем.
🔍 Как это работает?
Допустим, есть базовый API-клиент с аутентификацией:
class BaseApiClient:
# Возвращает токен для аутентификации.
def get_auth_token(self) -> str:
raise NotImplementedError
# Авторизованный запрос
def send_request(self, url: str) -> Response:
token = self.get_auth_token()
headers = {"Authorization": token}
return requests.get(url, headers=headers)
Ок, теперь реализуем двух клиентов:
class AuthClient(BaseApiClient):
def get_auth_token(self) -> str:
return "Bearer token" # Корректный токен
class NoAuthClient(BaseApiClient):
def get_auth_token(self) -> str:
return None # вот тут уже подозрительно
А дальше кто-то пишет код, который работает с клиентом:
def test_send_request(client: BaseApiClient, url: str):
url = "http://some-api.com/endpoint"
return client.send_request(url)
С AuthClient все ок. С NoAuthClient словим баг: {"Authorization": None }.
Почему?
Потому, что поведение наследника не соответствует ожиданиям.
✅ Как правильно:
Если NoAuthClient не поддерживает аутентификацию, не нужно заставлять его реализовывать метод, который делает вид, что всё ок.
Лучше изменить архитектуру:
- вынести токен в отдельный класс (AuthProvider)
- если часть клиентов не поддерживает аутентификацию, вынесем эту логику в отдельный класс/интерфейс:
class AuthProvider(ABC):
@abstractmethod
def get_auth_token(self) -> str:
pass
class Auth(AuthProvider):
def get_auth_token(self) -> str:
return "Bearer token" # Корректный токен
class BaseApiClient:
def send_request(
self,
url: str,
headers: Optional[dict] = None
) -> requests.Response:
return requests.get(url, headers=headers)
class AuthApiClient(BaseApiClient):
def __init__(self, auth: AuthProvider):
self.auth = auth
def send_request(self, url: str, **kwargs) -> requests.Response:
token = self.auth.get_auth_token()
headers = kwargs.get("headers", {})
headers["Authorization"] = token
return super().send_request(url, headers=headers)
Теперь клиент отработает без ошибок.
# и этот клиент
client = AuthApiClient(RealAuth())
# и этот клиент отработает
client = BaseApiClient()
def test_send_request(client: BaseApiClient, url: str):
url = "http://some-api.com/endpoint"
return client.send_request(url)
Вывод:
Соблюдение LSP:
🔹 Делает поведение предсказуемым
🔹 Спасает от скрытых багов
🔹 Позволяет переиспользовать код без костылей
——————————-
📱 TG-сообщество
📱 Обучение
📱 Отзывы🚀 Открыл набор на потоки REST API Advanced и Professional!
Если ты автоматизатор, и до сих пор не с этими навыками — ты просто теряешь в деньгах.
Выкладываю один из самых интересных моментов прошедшего вебинара, где менее чем за минуту ⏱️ мы разворачиваем целый фреймворк!
📊 И немного про ступени.
🎓 Advanced — это не “чуть сложнее”, это переход на уровень, где:
- ты больше не “дописываешь тесты” — ты строишь фреймворк, и делаешь это правильно
- ты шаришь в микросервисной архитектуре, авторизации, логах, отчётах и Telegram-нотификациях
- ты понимаешь, как устроен CI/CD и как тесты встраиваются в процесс поставки продукта.
- можешь дать оценку покрытия своего сервиса автотестами.
🏆 Professional — для тех, кто хочет делать так, как делают в top-компаниях:
- асинхронность, CLIs, генерация кода и фикстур, тестов, проектные шаблоны, управление зависимостями и качество кода на уровне команды
- ты не просто пишешь код — ты делаешь инструменты, которые экономят часы другим
- после этого курса ты больше не “один из”, ты становишься носителем экспертизы.
Как вы знаете я работаю и с юридическими лицами.
Почему стоит согласовать обучение с работодателем прямо сейчас:
✅ Это не теория — ты сразу начнёшь внедрять полезные штуки в проект.
✅ Можно частично или полностью компенсировать курс через обучение от компании.
✅ Это прямой вклад в эффективность команды, стабильность автотестов и ускорение поставок.
Почему надо шевелиться:
🔹 Поток стартует скоро — 12 мая.
🔹 Кол-во мест ограничено, чтобы каждому было внимание.
🔹 Если ты от компании, нужно успеть согласовать нужные документы.
🔹 В следующем квартале ты или уже покажешь рост, или опять скажешь “вот в следующем точно пройду”.
Готов? Тогда выбирай курс и вписывайся:
• 🎓 Advanced
• 🏆 Professional
И если сомневаешься, с какого лучше начать — напиши, помогу определиться. 💬
O – Open/Closed Principle (OCP)
Код должен быть открыт для расширения, но закрыт для модификации.
Рассмотрим пример из предыдущего поста, где `KafkaProducerManager` принимает продюсер в конструкторе. Это один из примеров OCP в действии.
🔍 Как это работает?
Допустим, нам нужно:
- Добавить логирование отправляемых сообщений
- Реализовать авторизацию при отправке в топик
- Ввести валидацию сообщений
Старая реализация (без OCP) потребовала бы изменений в `KafkaProducerManager` что добавляет риск сломать существующую логику.
Новая реализация (с OCP) позволяет расширить функционал без модификации основного класса.
Реализация:
1. Определяем базовый интерфейс:
from abc import ABC, abstractmethod
class BaseProducer(ABC):
@abstractmethod
def send(self, message: type[BaseModel]) -> None:
pass
2. Реализуем конкретный продюсер:
class LoggedProducer(BaseProducer):
def __init__(self, producer: KafkaProducer):
self.producer = producer
def send(self, message: str) -> None:
print(f"Отправка сообщения: {message}")
return self.producer.send(message)
3. Модифицируем менеджер:
Изменяем аннотацию указывая, что теперь принимаем любой совместимый продюсер
class KafkaProducerManager:
def __init__(self, producer: BaseProducer)
self.producer = producer
def send_message(self, message: type[BaseModel]):
msg = message.model_dump_json()
return self.producer.send(msg)
4. 💻 Пример использования:
# Базовый продюсер
producer = KafkaProducer()
# Расширенный функционал (логирование)
logged_producer = LoggedProducer(producer)
# Менеджер работает с любым продюсером
producer_manager = KafkaProducerManager(logged_producer)
message = Message(body='some value', key='some_key')
producer_manager.send(message) # Логируется и отправляется сообщение
Преимущества:
1. Безопасность - Не трогаем рабочий код
2. Гибкость - Легко добавляем новые фичи
3. Масштабируемость - Можно создать `ValidatedProducer`, `AuthProducer` и т.д.
👉 Главное: Мы расширили функционал, не изменяя существующий код `KafkaProducerManager`, именно в этом суть OCP.
——————————-
📱 TG-сообщество
📱 Обучение
📱 Отзывы📌 S – Single Responsibility Principle (SRP)
Принцип единственной ответственности
"Один класс — одна ответственность."
Нарушение этого принципа — одна из самых распространённых проблем в коде. Рассмотрим пример, который прислал мне мой друг.
Самое забавное, что этот код ему помог написать ChatGPT.
На самом деле он пришел с другой проблемой, но я сразу заметил тут несколько проблем и первая, что данный класс SupplyMessageBuilder берет на себя слишком много:
class SupplyMessage:
def __init__(self):
self.site_id = None
self.ml_supplies = []
def __str__(self):
return json.dumps({"SITEID": self.site_id, "MLSUPPLIES": self.ml_supplies}, indent=4)
class SupplyMessageBuilder:
def __init__(self):
self.message = SupplyMessage()
def set_site_id(self, site_id):
self.message.site_id = site_id
return self
def add_ml_supply(self, ml_supply_id, ml_supply_ship_dt):
supply = {
"MLSUPPLYID": ml_supply_id,
"MLSUPPLYSHIPDT": ml_supply_ship_dt,
"ORDERS": [],
}
self.message.ml_supplies.append(supply)
return self
...
@staticmethod
def create_producer():
return KafkaProducer() # Нарушение SRP!
В чём проблема?
Класс `SupplyMessageBuilder` выполняет две задачи:
1. Формирует сообщение (строит JSON).
2. Создаёт Kafka Producer.
Это нарушает SRP, потому что:
- У каждого билдера свой продюссер, и если нужно будет изменить логику Kafka, придется перелопачивать все билдеры.
- Много дублирования, в исходном коде конструктор KafkaProducer заполнялся в каждом билдере.
- Увеличивает риск ошибок при модификации.
- Размазана зона ответственности.
✅ Как исправить?
Разделить ответственности:
1. `SupplyMessageBuilder` – только формирует сообщение.
2. `KafkaProducerManager` – отвечает за работу с Kafka.
Исправленный код:
class SupplyMessageBuilder:
... # Только методы для построения сообщения
def build(self) -> SupplyMessage:
return SupplyMessage.model_validate_json(self.message)
class KafkaProducerManager:
def __init__(self, producer: KafkaProducer)
self.producer = producer
def send_message(self, message: type[BaseModel]):
msg = message.model_dump_json()
return self.producer.send(msg)
Теперь каждый класс отвечает только за свою зону ответственности.
1. SupplyMessageBuilder только билдит и валидирует сообщения (для сообщения добавлен Pydantic, но это сюда не стал писать)
2. KafkaProducerManager занимается только отправкой сообщений
3. KafkaProduccer передается как зависимость
Вывод
Соблюдение SRP делает код:
🔹 Чище – проще читать и понимать.
🔹 Гибче – легче вносить изменения.
🔹 Надёжнее – меньше неожиданных багов.
——————————-
📱 TG-сообщество
📱 Обучение
📱 ОтзывыПод одним из постов меня попросили рассказать о принципах SOLID.
Исходя из моего опыта, построение архитектуры фреймворка — тема довольно шаблонная, и особо упарываться над какими-то моментами не стоит. Средняя подготовка автоматизатора обычно ниже, чем у разработчика, поэтому главное, чтобы тесты писались — и желательно писались быстро. Тем не менее, применение некоторых принципов SOLID может помочь избежать проблем с поддержкой кода.
🔍 Почему SOLID важен в автоматизации?
Ключевые моменты:
- SOLID делает код более гибким, переиспользуемым и удобным для поддержки.
- Автотесты часто страдают от «спагетти-кода» — SOLID снижает этот риск.
- Принципы применимы к хелперам, API-клиентам, обработчикам данных и другим компонентам.
📌 5 принципов SOLID
1️⃣ S – Single Responsibility (Принцип единственной ответственности)
2️⃣ O – Open/Closed (Принцип открытости/закрытости)
3️⃣ L – Liskov Substitution (Принцип подстановки Лисков)
4️⃣ I – Interface Segregation (Принцип разделения интерфейсов)
5️⃣ D – Dependency Inversion (Принцип инверсии зависимостей)
SOLID — это не строгие правила, а гибкие принципы, которые помогают избегать хаоса в коде.
В следующих постах разберём каждый принцип на примерах из автоматизации.
——————————-
📱 TG-сообщество
📱 Обучение
📱 Отзывы
+2
НАПОМИНАЮ, ЧТО СЕГОДНЯ ПОСЛЕДНИЙ ДЕНЬ ДЕЙСТВИЯ ПРОМОКОДА СО СКИДКОЙ 10%
Более подробное описание тренингов можно найти тут:
Программа STARTER
Программа ADVANCED
Программа PROFESSIONAL
Начало обучения уже в этот понедельник 07.04.2025!
اکنون در دسترس! پژوهش تلگرام ۲۰۲۵ — مهمترین بینشهای سال 
