我具有使用text
和attachments
构建并返回Slack消息的功能。如何重构此功能以使其更易于测试?我应该将其拆分为多个功能吗?
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 = 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)
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
}
这是私有功能:
def _build_filters(resource_type, availability):
resource_types = [
{"text": "All types", "value": "all"},
{"text": ":link: Webpages", "value": "web_pages"}
]
availability_choices = [
{"text": "Available / Unavailable", "value": "all"},
{"text": ":white_circle: Available", "value": "available"},
{"text": ":red_circle: Unavaliable", "value": "unavailable"}
]
selected_resource_types = list(filter(
lambda t: t['value'] == resource_type, resource_types))
selected_availability_choices = list(filter(
lambda a: a['value'] == availability, availability_choices))
return {
"fallback": "Resource filters",
"color": "#d2dde1",
"mrkdwn_in": ["text"],
"callback_id": "resource_filters",
"actions": [
{
"name": "resource_type",
"text": "Type",
"type": "select",
"options": resource_types,
"selected_options": selected_resource_types
},
{
"name": "resource_availability",
"text": "Available",
"type": "select",
"options": availability_choices,
"selected_options": selected_availability_choices
}
]
}
def _build_page_item(page, user):
return {
"fallback": "Page",
"color": page.status_color,
"mrkdwn_in": ["fields"],
"callback_id": 'page_change',
"fields": [
{
"title": page.title,
"value": f"_Page_ ({page.status})"
},
{
"title": "URL",
"value": page.url
}
],
"footer": _build_resource_footer(page),
"actions": _build_resource_item_actions(page, user)
}
def _build_async_task_item(async_task, user):
return {
"fallback": "Async task",
"color": async_task.status_color,
"mrkdwn_in": ["fields"],
"callback_id": 'async_task_change',
"fields": [
{
"title": async_task.title,
"value": f"_Async task_ ({async_task.status})"
},
{
"title": "URL",
"value": async_task.url
}
],
"footer": _build_resource_footer(async_task),
"actions": _build_resource_item_actions(async_task, user)
}
答案 0 :(得分:1)
您几乎应该确定将其拆分为多个功能。您将拥有更少的更简单的测试用例。
让我们看看如何做到这一点。因为您希望添加更多测试,所以我会坚持安全重构,您可以在像PyCharm这样的IDE中进行此操作。
注意:我正在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
}
因此,现在您可以为page_and_async_task
编写6个测试,然后在测试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
}
现在,您可以测试顶级功能,模拟它的助手。然后,对于您制作的每个模拟,您都需要对帮助程序进行测试,以显示其行为与模拟完全相同,一直到最后。然后编写一个运行真实事物并显示所有功能协同工作的测试。
在此之后的步骤中,您可能希望应用策略模式来简化注入辅助功能的过程。