我的Tornado应用程序有一个处理程序,它使用AsyncHTTPClient
调用另一个外部API,然后返回。
如何编写测试我的处理程序正确调用外部API的单元测试?
我觉得我应该在我的单元测试中运行第二台服务器,该服务器模拟外部服务器并提供与我的处理程序相同的API。我可以提供URI作为我的应用程序的参数,因此它不是硬编码的。但是,我不确定如何在AsyncHTTPTestCase
中正确地启动2台服务器。
答案 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_sms
,from ali_sms import ali_send_code_sms
然后import ali_sms