Python 如何测试Tornado处理程序是否正确调用了另一个API?

Python 如何测试Tornado处理程序是否正确调用了另一个API?,python,tornado,Python,Tornado,我的Tornado应用程序有一个处理程序,它使用AsyncHTTPClient调用另一个外部API,然后返回 我如何编写一个单元测试来测试我的处理程序是否正确调用了外部API 我觉得我应该在我的单元测试中运行第二台服务器,它模拟外部服务器并提供与我的处理程序所使用的相同的API。我可以将要命中的URI作为参数提供给我的应用程序,因此它不是硬编码的。但是,我不确定如何在AsyncHTTPTestCase中(正确地)启动2台服务器。关于如何分解和正确处理测试,有很多意见。对于我测试必须与第三方对话的

我的Tornado应用程序有一个处理程序,它使用
AsyncHTTPClient
调用另一个外部API,然后返回

我如何编写一个单元测试来测试我的处理程序是否正确调用了外部API


我觉得我应该在我的单元测试中运行第二台服务器,它模拟外部服务器并提供与我的处理程序所使用的相同的API。我可以将要命中的URI作为参数提供给我的应用程序,因此它不是硬编码的。但是,我不确定如何在
AsyncHTTPTestCase

中(正确地)启动2台服务器。关于如何分解和正确处理测试,有很多意见。对于我测试必须与第三方对话的内容的场景,在您的示例中,我将其分为3个部分,一个非常薄的部分,只与API对话。然后,我将编写一个集成测试来测试与API对话的部分。然后,我将制作一个模拟或其他测试装置,用于替换对API的调用,并编写处理API调用的代码。这可以很容易地放在单元测试中,因为它通常类似于:

my_new_data = do_process(call_my_third_party_api())
只需传入第三方api调用返回的数据的模拟,就可以轻松地为do_进程编写测试

然后,当测试处理程序时,您只需使用返回您希望从第三方获得的结果的东西模拟api调用

现在您有三个不同的测试来测试应用程序的每个区域。可以运行的测试,以确保代码正确访问API。告诉您是否正确处理API处理的测试,以及告诉您是否正确将该信息返回给最终用户的第三个测试。

您可以使用模拟库(python3.5中的unittest.mock):


我也有同样的问题

这是我在python3.5中的解决方案

handler.py

class VerificationCodeHandler(BaseRequestHandler):

    @asynchronous
    @gen.coroutine
    def post(self):
        code_type = self.body_argument.get('code_type', 'register')
        phone_number = self.body_argument.get('phone_number')
        # 发送阿里云短信
        try:
            # This is another API, encapsulate into a coroutine
            send_result = yield ali_sms.ali_send_code_sms(phone_number, code_type, code)
            http_code, send_sms_result = send_result.code, json.loads(send_result.body.decode('utf-8'))
            if int(http_code) == 200:
                if send_sms_result.get('Code').upper() == 'OK':
                    self.success(self.prompt_msg.AUTH_SEND_MSG_SUCCESS)
                    # 缓存数据
                    self.redis.set(key_lasttime, 'send', ex=settings.SMS_INTERVAL)
                    self.redis.set(key_times,
                                   int(is_overrun) + 1 if is_overrun else 1,
                                   ex=settings.SMS_USER_LIMIT)
                    self.redis.set(key_code, code, ex=settings.SMS_EXPIRATION)
                else:
                    self.fail(send_sms_result.get('Message', self.prompt_msg.AUTH_SEND_MSG_ERROR))
            else:
                self.fail(self.prompt_msg.AUTH_SEND_MSG_ERROR_AGAIN)
        except:
            # TODO 系统异常,需要通知管理员
            logger.exception('发送短信失败')
            self.fail(self.prompt_msg.AUTH_SEND_MSG_ERROR_AGAIN)
ali_sms.py

async def ali_send_code_sms(phone_number, code_type, code):
    url = get_aliyun_sms_url(phone_number, code_type, code)
    http_client = AsyncHTTPClient()
    resp = await http_client.fetch(url)
    return resp
unittest.mock

class VerificationCodeTestCast(BaseHandelrTestCase):

    @mock.patch('common.aliyun.sms.ali_send_code_sms')
    def test_send_sms_fail(self, sms):
        fetch_future = tornado.concurrent.Future()
        fetch_future._result = mock.Mock(body=json_encode({'Code': 'OK'}).encode('utf-8'), code=200)
        fetch_future._done = True
        sms.return_value = fetch_future
        resp = self.fetch('/get_ver_code/', method='POST',
                      body=json_encode({'code_type': 'register',
                                        'phone_number': '17980888160'})
                      )
        result = json_decode(resp.body)
        self.assertEqual(200, resp.code)
        self.assertEqual(400, result.get('code'))
        self.assertEqual(result['msg'], '今日发送短信超限,请明日再试')
注:
在处理程序中使用
ali\u-send\u-code\u-sms
时,不要从ali\u-sms导入ali\u-send\u-code\u-sms,
import-ali\u-sms
然后
ali\u-send\u-code\u-sms
,如果我理解正确,您想测试
asynchtpclient
是否按预期工作?我想龙卷风的单元测试已经涵盖了这一点。如果你想测试你的呼叫是否成功,你真的不能;即使您有一个测试服务并测试调用它,它也与实际服务的工作方式毫无关系(换言之:您的测试可能通过,但真正的服务可能失败)。您应该专注于测试您引入的功能,例如处理服务的响应(可以模拟),而不是试图覆盖您无法控制的内容。@BerislavLopac感谢您的评论。我不是要测试
AsyncHTTPClient
,而是测试我的处理程序是否创建了正确的主体,是否正确调用了外部API,是否正确处理了响应。是的,我不控制外部API,但我可以控制如何与它交互,这就是我要测试的。我明白你的意思。然而,这看起来像是三个独立的职责,理想情况下应该是在不同的方法中。测试身体创建和响应处理程序。这实际上是我最后做的。这不是一个完整的端到端测试,如果不使用实际的外部API,这甚至是不可能的,但至少有更少的错误空间。
class VerificationCodeTestCast(BaseHandelrTestCase):

    @mock.patch('common.aliyun.sms.ali_send_code_sms')
    def test_send_sms_fail(self, sms):
        fetch_future = tornado.concurrent.Future()
        fetch_future._result = mock.Mock(body=json_encode({'Code': 'OK'}).encode('utf-8'), code=200)
        fetch_future._done = True
        sms.return_value = fetch_future
        resp = self.fetch('/get_ver_code/', method='POST',
                      body=json_encode({'code_type': 'register',
                                        'phone_number': '17980888160'})
                      )
        result = json_decode(resp.body)
        self.assertEqual(200, resp.code)
        self.assertEqual(400, result.get('code'))
        self.assertEqual(result['msg'], '今日发送短信超限,请明日再试')