Вы почти наверняка должны разделить это на несколько функций. У вас будет меньше тестовых случаев, которые будут проще.
Давайте посмотрим, как это сделать. Поскольку вы хотите добавить больше тестов, я остановлюсь на безопасном рефакторинге, который вы можете выполнять в среде IDE, такой как PyCharm.
ПРИМЕЧАНИЕ: я делаю этот рефакторинг в редакторе ответов SO, поэтому он не проверен. Возможны опечатки или пропущенные параметры.
Первое, что бросается в глаза, это то, что у вас есть несколько заданий на pages
и async_tasks
def build_list_message(team_id, user_id, msg_state=None, chl_state=None):
...
pages = Page.objects.none()
async_tasks = AsyncTask.objects.none()
if resource_type in ['web_pages', 'all']:
pages = Page.objects.filter(
user__team__team_id=team_id).order_by('title')
if resource_type in ['async_tasks', 'all']:
async_tasks = AsyncTask.objects.filter(
user__team__team_id=team_id).order_by('title')
if availability == 'available':
pages = pages.filter(available=True)
async_tasks = async_tasks.filter(available=True)
elif availability == 'unavailable':
pages = pages.filter(available=False)
async_tasks = async_tasks.filter(available=False)
channel_id = chl_state.get('channel_id')
if channel_id:
pages = pages.filter(alert_channel=channel_id)
async_tasks = async_tasks.filter(alert_channel=channel_id)
...
Похоже, что это связные значения, основанные на коде, поэтому я собираюсь извлечь одну функцию, которая выбирает их.
def page_and_async_task(resource_type, availability, team_id, chl_state):
pages = Page.objects.none()
async_tasks = AsyncTask.objects.none()
if resource_type in ['web_pages', 'all']:
pages = Page.objects.filter(
user__team__team_id=team_id).order_by('title')
if resource_type in ['async_tasks', 'all']:
async_tasks = AsyncTask.objects.filter(
user__team__team_id=team_id).order_by('title')
if availability == 'available':
pages = pages.filter(available=True)
async_tasks = async_tasks.filter(available=True)
elif availability == 'unavailable':
pages = pages.filter(available=False)
async_tasks = async_tasks.filter(available=False)
channel_id = chl_state.get('channel_id')
if channel_id:
pages = pages.filter(alert_channel=channel_id)
async_tasks = async_tasks.filter(alert_channel=channel_id)
return pages, async_tasks
def build_list_message(team_id, user_id, msg_state=None, chl_state=None):
if not msg_state:
msg_state = {}
if not chl_state:
chl_state = {}
resource_type = msg_state.get('resource_type', 'all')
availability = msg_state.get('resource_availability', 'all')
pages, async_tasks = page_and_async_task(resource_type, availability, team_id, chl_state)
user = SlackUser.retrieve(team_id, user_id)
attachments = [
_build_filters(resource_type, availability),
*[_build_page_item(p, user) for p in pages],
*[_build_async_task_item(at, user) for at in async_tasks]
]
return {
'text': "Here's the list of all monitoring resources",
'attachments': attachments
}
Итак, теперь вы можете написать 6 тестов для page_and_async_task
, а затем смоделировать эту функцию при тестировании build_list_message
. Имитирующая функция просто должна возвращать действительные страницы и async_tasks.
Далее я собираюсь рассмотреть принцип единой ответственности. Ваша функция отвечает за построение диктата с помощью клавиш text
и attachments
. Он может делегировать что-то еще.
def build_list_message(team_id, user_id, msg_state=None, chl_state=None):
if not msg_state:
msg_state = {}
if not chl_state:
chl_state = {}
resource_type = msg_state.get('resource_type', 'all')
availability = msg_state.get('resource_availability', 'all')
pages, async_tasks = page_and_async_task(resource_type, availability, chl_state)
user = SlackUser.retrieve(team_id, user_id)
attachments = make_attachments(resource_type, availability, pages, async_tasks, user)
return {
'text': "Here's the list of all monitoring resources",
'attachments': attachments
}
def make_attachments(resource_type, availability, pages, async_tasks, user):
return [
_build_filters(resource_type, availability),
*[_build_page_item(p, user) for p in pages],
*[_build_async_task_item(at, user) for at in async_tasks]
]
Точка входа по-прежнему делает слишком много - она разбивает входные данные, вызывает SlackUser.retrieve, который, по-видимому, обращается к базе данных, и создает сообщение.
def build_list_message(team_id, user_id, msg_state=None, chl_state=None):
if not msg_state:
msg_state = {}
if not chl_state:
chl_state = {}
resource_type = msg_state.get('resource_type', 'all')
availability = msg_state.get('resource_availability', 'all')
user = SlackUser.retrieve(team_id, user_id)
return _build_list_message(team_id, user_id, resource_type, availability, chl_state, user)
def _build_list_message(resource_type, availibility, chl_state, user):
pages, async_tasks = page_and_async_task(resource_type, availability, chl_state)
attachments = make_attachments(resource_type, availability, pages, async_tasks, user)
return {
'text': "Here's the list of all monitoring resources",
'attachments': attachments
}
Теперь вы можете протестировать функцию верхнего уровня, высмеивая ее помощников. Затем за каждый сделанный вами макет вы должны пройти тест для помощника, который показывает, что он ведет себя так же, как и макет, вплоть до самого конца. Затем напишите один тест, который запускает реальную вещь и показывает, что все работает вместе.
В следующих шагах вы можете применить шаблон стратегии, чтобы упростить внедрение вспомогательных функций.