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

时间:2016-10-19 18:22:28

标签: python tornado

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

如何编写测试我的处理程序正确调用外部API的单元测试?

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

3 个答案:

答案 0 :(得分:2)

关于如何分解和正确处理测试,有很多意见。对于我正在测试必须与第三方交谈的内容的场景,在您的示例中,我将其分为3个部分,这是一个非常薄的部分,只是与API进行对话。然后我会编写一个集成测试来测试与API对话的部分。然后我会创建一个模拟或其他测试夹具,可以替换对API的调用并编写处理API调用的代码。这可以很容易地放在单元测试中,因为它通常是这样的:

my_new_data = do_process(call_my_third_party_api())

只需传入第三方api调用返回的数据模拟,即可轻松编写do_process测试。

然后,当它有时间测试你的处理程序时,你只需要用你想要从第三方获得的结果来模拟你的api调用。

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

答案 1 :(得分:1)

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

import unittest
from cStringIO import StringIO

from mock import patch

import tornado.web
import tornado.httpclient
from tornado.testing import AsyncHTTPTestCase


class TestHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    def get(self):
        def request_cb(response):
            self.finish(response.body)

        http_client = tornado.httpclient.AsyncHTTPClient()
        request = tornado.httpclient.HTTPRequest('http://example.com')
        http_client.fetch(request, request_cb)

class TestHttpService(AsyncHTTPTestCase):
    def setUp(self):
        super(TestHttpService, self).setUp()
        self.async_client = tornado.httpclient.AsyncHTTPClient(self.io_loop)
        self.async_patcher = patch('tornado.httpclient.AsyncHTTPClient')
        self.mocked_async_client = self.async_patcher.start()

    def get_app(self):
        return application

    def test_async_httpclient(self):
        request = tornado.httpclient.HTTPRequest('http://example.com')
        response = tornado.httpclient.HTTPResponse(request, 200,
                                buffer=StringIO('test'))
        self.mocked_async_client().fetch.side_effect = lambda x,y: y(response)
        self.async_client.fetch(self.get_url('/'), self.stop)
        resp = self.wait()
        self.assertEquals(resp.body, 'test')

application = tornado.web.Application([(r'/', TestHandler)])

答案 2 :(得分:0)

我遇到了同样的问题。

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

handler.py

int mini = INT_MAX, a, b;
for (int i=0, j = ordered_array.size() -1 ; i <j;) {
    int tmp = ordered_array[i] - ordered_array[j];
    if (abs(tmp - target) < mini) {
             mini = abs(tmp - target); 
             a = i;
             b = j;
       }
    if (tmp == target) return {i,j}; 
    else if (tmp > target) j --; 
    else i ++; 
}
return {a,b};

ali_sms.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)

unittest.mock

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

注意: 在处理程序中使用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_send_code_smsfrom ali_sms import ali_send_code_sms然后import ali_sms