我需要使用模拟吗?

Do I need to use mocks?

我有处理来自 Slack 的特定机器人事件的功能。一般来说,用户点击一个按钮然后我的服务器接收并处理这个按钮的负载。

问题是我应该如何测试它?我需要模拟 make_adminbuild_admins_message 并检查它们是否被调用,或者我需要测试真实的实现吗?例如,我可以从数据库中检索用户并检查它实际上是管理员,还检查 build_admins_message returns 我希望收到的字典。

@slack_interactions.on('admin_add')
def handle_admin_add(payload):
    team_id = payload['team']['id']
    user_id = payload['user']['id']
    action_value = payload['actions'][0]['selected_options'][0]['value']

    user = SlackUser.objects.find_by_ids(team_id, action_value)

    if user and not user.is_bot:
        user.make_admin()

    return build_admins_message(team_id, user_id)

目前我的测试是这样的:

class TestAdminAddHandler(TestCase):
    def setUp(self):
        team = SlackTeam.objects.create(team_id='TEAMID')
        SlackUser.objects.create(team=team, user_id='USERID')
        SlackUser.objects.create(team=team, user_id='BOTID', is_bot=True)
        SlackUser.objects.create(
            team=team, user_id='ADMINID', is_bot_admin=True)

    def tearDown(self):
        SlackUser.objects.all().delete()
        SlackTeam.objects.all().delete()

    def test_wrong_callback(self):
        payload = {'callback_id': 'wrong_callback'}
        message = handle_admin_add(payload)
        self.assertIsNone(message)

    def test_has_no_user(self):
        payload = {
            'callback_id': 'admin_add',
            'team': {'id': 'TEAMID'},
            'user': {'id': 'ADMINID'},
            'actions': [{
                'selected_options': [{'value': 'BADID'}]
            }]
        }

        message = handle_admin_add(payload)

        user = SlackUser.objects.get(user_id='USERID')
        self.assertFalse(user.is_bot_admin)

        for att in message['attachments']:
            self.assertNotIn('BADID', att.get('title', ''))

    def test_user_is_bot(self):
        payload = {
            'callback_id': 'admin_add',
            'team': {'id': 'TEAMID'},
            'user': {'id': 'ADMINID'},
            'actions': [{
                'selected_options': [{'value': 'BOTID'}]
            }]
        }

        message = handle_admin_add(payload)
        user = SlackUser.objects.get(user_id='BOTID')
        self.assertFalse(user.is_bot_admin)

        for att in message['attachments']:
            self.assertNotIn('BOTID', att.get('title', ''))

    def test_add_admin(self):
        payload = {
            'callback_id': 'admin_add',
            'team': {'id': 'TEAMID'},
            'user': {'id': 'ADMINID'},
            'actions': [{
                'selected_options': [{'value': 'USERID'}]
            }]
        }

        message = handle_admin_add(payload)

        user = SlackUser.objects.filter(user_id='USERID').first()
        self.assertTrue(user.is_bot_admin)

        user_in_list = False
        for att in message['attachments']:
            if 'USERID' in att.get('title', ''):
                user_in_list = True

        self.assertTrue(user_in_list)

这里的问题有两个。首先,您必须验证您的代码是否可以与正常运行的 Slack 服务器一起正常工作——正如您推断的那样,模拟将是执行此操作的好方法,因为单元测试应该是完全独立的。您还可以编写模拟来模拟功能不正常的服务器的行为。

但是,这可能会导致您的模拟无法正确模拟 Slack 服务器的行为,因此即使您的代码通过了单元测试,它也无法在现实生活中运行。为此,您需要 integration 测试来验证(正如您当前的测试 class 所显示的那样)代码是否可以在 Slack 服务器上正常工作。

创建模拟对象时,您甚至可以从成功的交易中捕获网络流量,然后使用该内容生成模拟响应,方法是修补较低级别的组件以生成适当的网络级别响应,以避免与服务器。由于您通常没有能力为测试方便而修改生产服务器,因此模拟通常是验证是否正确处理异常服务器响应的最简单方法。这完全取决于你想走多远。

单元测试应该验证单个组件的功能,而不应该依赖任何外部服务。集成测试验证代码与其他组件一起正常运行,通常只有在验证了各个组件的完整性后才会执行。

测试是一个很大的主题,所以我希望这能回答您的问题。