Pull to refresh

Comments 8

Было бы забавно, но длинновато. Лучше сжатый краткий пересказ основных идей оттуда применительно к исходному проекту

Кроме наследования, всё остальные столпы ООП в коде пристутствуют.

Когда руками зависимости собираешь, то без разницы что передавать функцию или класс. В примере я использую функции, а по жизни часто callable классы, их настраивать удобно.

Для аннотаций типов использую Protocol, тогда анализаторы принимают и класс и функцию.

from typing import Protocol


class Uploader(Protocol):
  def __call__(self, path, data):
    ...


class UploadClass(Protocol):
  def __init__(self, session):
    self._sesion = session

  def __call__(self, path, data):
    return self._sesion.post(path, data)

Не до конца я понял вашу идею. Как-то очень запутанно получилось.
Я бы сделак что-то вроде:

# инкапсулируем логику работы с API  в отдельный класс
class Api:
  def fetch_data(): ...
  def save_data(data:): ...

# это по сути основная бизнес логика приложения
def update_data(data):
  return [fileld.upper() for field in data]

# ну а это наш контроллер
def do_work():
  api = Api()
  data = api.fetch_data()
  updated_data = update_data(data)
  api.save(updated_data)


Тестировал бы используя подход outside-in, т.е. мокировал бы библиотеку requests и проверял бы, что мы отправляем то, что API ожидает.

def test_api(requests_mock):
  do_work()
  requests_mock.assert_called_with(['FIELD1', 'FIELD2', 'FIELD3'])


Ну и если `data` это достаточно сложный объект, мжно добавиь `dataclass` и дополнительно сериализацию/десериализацию, в таком случае из `Api` будем возвращать готовые объекты, а не словарь, соответственно можно будет в этот класс добавить функции манипуляции с объектом.

Этот код аналог второго примера. А сложность она вся в третьем.

Контекст сириус бизнес, это готовность к изменениям. Под готовностью для изменений я подразумеваю возможность добавлять новый функционал, не трогая существующий.

Ваш пример решает текущую задачу в этом контексте он хороший. Но если его понадобится сильно расширить, то в этом контексте я бы назвал его плохим. Добавляя в него новые фичи без рефакторинга, вы получите крупный монолит.

Я не вижу особого отличия от того, чтобы вызвать функцию или создать экземпляр класса и вызвать его метод. Немного по разному код скомпонован, но верхнеуровневая функция жестко зависит от внешнего кода и там, и там.

В функции есть три подвижные части, которые могут менять по разным причинам: upload/download/logic

test_api(requests_mock)

В данном тесте мы мокаем 2 из них да и еще и одинаковым моком. То есть после развития этого кода, окажется, что в каждый тест надо 2-3 мока передавать. Для дальгейшего развития такой подход обернется адом. Так же это плохо паралелится, один человек меняет загрузку , другой скачивание и оба тесты.

3 подвижных части, даже если взять по 2 варианте, успех и ошибка, то это 8 сценариев для outside-in тестирования. Логичекси исключим сценарии после ошибки, будет 5 тестов.
Для 100% покрытия важной логики такой подход мне кажется сложным, количество тестов растёт экспоненциально относительно подвижных частей.

Sign up to leave a comment.

Articles