Комментарии 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% покрытия важной логики такой подход мне кажется сложным, количество тестов растёт экспоненциально относительно подвижных частей.
Не обязательно так делать.
json.loads(data)
Лучше так:
import requests
r = requests.get('https://api.github.com/events')
r.json()
О плохом и хорошем коде