Наша платформенная команда старается помочь, как можем, но не нарушая хорошие практики.
У нас есть платформенный фреймворк, который может применяться для всего: для ML, разработки, тестов.
У него очень крутая концепция и очень крутой интерфейс.
Представьте, что вы пишете автотесты, и любой клиент, который вам нужен, можно получить, просто проставив декоратор.
Например:
@nuke_test
async def test_something(client: MyClient):
await client.do_something()
То есть вам даже не нужно писать фикстуру, вы можете сделать любой класс-хелпер.
Просто поставить аннотацию, и он будет работать, не надо никаких инстансов — всё работает.
Даже если ваш клиент имеет кучу зависимостей типа:
class MyClient(Client):
api1: Api1
api2: Api2
api3: Api3
...
apiN: ApiN
Вам эти зависимости не нужно передавать — тест будет работать так же просто, как в первом примере.
Потому что у нашей платформы написан свой DI и определённый механизм, который скрыто для пользователя создаёт все зависимости.
Круто? Очень круто.
Удобно? Безумно удобно, интерфейс предельно простой.
Работает ли это на практике?
Не у всех. Почему? Потому что огромные ошибки проектирования тестов, оверинжиниринг, наследование не там, где нужно, нарушение принципов программирования.
Сегодня встречался ещё с одной командой, у которых тысячи тестов.
Вроде бы вся сложность реализации скрыта, но всё просто капец какое ломучее.
И ни один стандартный наш подход не работает. Почему?
Например:
from lib import clients
@dataclass
class BaseOrder:
id: int
class Order(BaseOrder):
@staticmethod
def do_something():
clients.do_something()
def test_order():
Order(id=1).do_something()
Что здесь не так?
1. Нарушение принципа SRP (Single Responsibility Principle)
Почему это плохо:
• Order должен представлять бизнес-сущность заказа.
• Но он также знает о том, как вызывать внешние сервисы.
• Это два разных уровня ответственности.
А что если завтра clients.do_something() изменит сигнатуру? Потребует аутентификацию? Появится новый клиент? Вам придётся менять модель Order.
Что хорошего?
Код простой — да. Но он не расширяемый. Попробуйте написать тест с моком или заменить clients на другую реализацию.
2. Нарушение принципа DIP (Dependency Inversion Principle)
from lib import clients — жёсткая зависимость
Почему это плохо:
• Order зависит от конкретной реализации, а не от абстракции.
• Невозможно заменить clients на другую реализацию без изменения Order.
• Нарушается принцип «зависеть от абстракций, а не от конкретики».
А что если понадобится другой clients для тестов?
А если clients переедет в другой модуль?
А если нужно будет добавить логирование или метрики вокруг вызовов?
3. Сильная связность
Почему это плохо:
• Order не может существовать без clients.
• clients нельзя заменить или замокать.
• Тестирование возможно только с реальным clients.
Если clients падает, падают все тесты Order.
Сложно тестировать логику Order изолированно.
4. Использование глобальной переменной, которая хранит все объекты.
Почему это плохо:
• Глобальное состояние может изменяться между тестами.
• Сложно контролировать состояние clients.
• Побочные эффекты между тестами.
Теперь представим, что мы хотим использовать наш подход с декоратором или фикстурой — и не можем.
Нам придётся явно передавать зависимости в каждый инстанс Order, что для тысячи тестов очень тяжело. У нас куча входных точек в каждом тесте.
Захотим немного поменять clients — есть вероятность разломать кучу тестов, потому что они ультра сильно связаны.
Как я считаю, можно было бы сделать правильно:
Сделать OrderHelper:
class OrderHelper:
def __init__(self, client):
self.client = client
def make_order(self, id):
self.client.do_something()
Тем самым мы получаем только ту зависимость, которая нам нужна, и не тащим за собой весь скоуп.
Продолжение следует...