我有一个Sanic应用程序,可以对外部api进行一些异步调用。我希望编写一些模拟这些外部调用的单元测试。
在下面的代码中,测试确实通过了,正如我们从日志中看到的那样。但是,在他们完成RuntimeError之后:抛出该事件循环已运行
简化的Sanic应用程序:
app = Sanic(__name__)
app.config.from_pyfile('/usr/src/app/config.py')
Initialize(
app,
access_token_name='jwt',
authenticate=lambda: True,
claim_aud=app.config.AUTH_JWT_TOKEN['service']['audience'],
claim_iss=app.config.AUTH_JWT_TOKEN['service']['issuer'],
public_key=app.config.AUTH_JWT_TOKEN['service']['secret'],
responses_class=JWTResponses
)
@app.listener('before_server_start')
def init(app, loop):
ssl_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ssl_ctx.load_cert_chain(app.config.SSL_CERT, app.config.SSL_CERT_KEY)
ssl_ctx.load_verify_locations(app.config.SSL_SERVER_CERT)
ssl_ctx.check_hostname = False
ssl_ctx.verify_mode = ssl.CERT_REQUIRED
conn = aiohttp.TCPConnector(ssl_context=ssl_ctx)
app.aiohttp_session = aiohttp.ClientSession(loop=loop, connector=conn)
access_logger.disabled = True
@app.listener('after_server_stop')
def finish(app, loop):
loop.run_until_complete(app.aiohttp_session.close())
loop.close()
@app.route("endpoint/<mpn>")
@protected()
async def endpoint(request, mpn):
msg = msg(
mpn,
)
headers = {'content-type': 'text/xml'}
async with session.post(
config.URL,
data=msg.tostring(pretty_print=True, encoding='utf-8'),
headers=headers,
) as response:
response_text = await response.text()
try:
response = (
Response.from_xml(response_text)
)
return response
except ResponseException:
logger.error(e.get_message()['errors'][0]['message'])
return response.json(
e.get_message(),
status=HTTPStatus.INTERNAL_SERVER_ERROR
)
if __name__ == '__main__':
app.run(host="0.0.0.0", port=8000)
这是测试:
from server import app as sanic_app
@pytest.yield_fixture
def app():
app = sanic_app
yield app
@pytest.fixture
def test_cli(loop, app, sanic_client):
return loop.run_until_complete(sanic_client(app))
token = jwt.encode(
{
"iss": (
sanic_app.config.AUTH_JWT_TOKEN['service']
['issuer']
),
"aud": (
sanic_app.config.AUTH_JWT_TOKEN['service']
['audience']
),
"exp": datetime.datetime.utcnow() + datetime.timedelta(
seconds=int(100)
)
},
sanic_app.config.AUTH_JWT_TOKEN['service']['secret'],
algorithm='HS256'
).decode('utf-8')
token = 'Bearer ' + token
async def test_success(test_cli):
with aioresponses(passthrough=['http://127.0.0.1:']) as m:
with open('tests/data/summary.xml') as f:
data = f.read()
m.post(
'https://external_api',
status=200,
body=data
)
resp = await test_cli.get(
'endpoint/07000000000',
headers={"Authorization": token}
)
assert resp.status == 200
resp_json = await resp.json()
assert resp_json == {SOME_JSON}
如上所述,测试通过了,但是引发了错误。
================================================================================================= ERRORS ==================================================================================================
____________________________________________________________________________________ ERROR at teardown of test_success ____________________________________________________________________________________
tp = <class 'RuntimeError'>, value = None, tb = None
def reraise(tp, value, tb=None):
try:
if value is None:
value = tp()
if value.__traceback__ is not tb:
raise value.with_traceback(tb)
> raise value
/usr/local/lib/python3.6/site-packages/six.py:693:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/usr/local/lib/python3.6/site-packages/six.py:693: in reraise
raise value
/usr/local/lib/python3.6/site-packages/six.py:693: in reraise
raise value
/usr/local/lib/python3.6/site-packages/pytest_sanic/plugin.py:212: in sanic_client
loop.run_until_complete(client.close())
uvloop/loop.pyx:1451: in uvloop.loop.Loop.run_until_complete
???
/usr/local/lib/python3.6/site-packages/pytest_sanic/utils.py:230: in close
await self._server.close()
/usr/local/lib/python3.6/site-packages/pytest_sanic/utils.py:134: in close
await trigger_events(self.after_server_stop, self.loop)
/usr/local/lib/python3.6/site-packages/pytest_sanic/utils.py:25: in trigger_events
result = event(loop)
server.py:84: in finish
loop.run_until_complete(app.aiohttp_session.close())
uvloop/loop.pyx:1445: in uvloop.loop.Loop.run_until_complete
???
uvloop/loop.pyx:1438: in uvloop.loop.Loop.run_until_complete
???
uvloop/loop.pyx:1347: in uvloop.loop.Loop.run_forever
???
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
> ???
E RuntimeError: this event loop is already running.
uvloop/loop.pyx:448: RuntimeError
任何帮助或建议,我们将不胜感激。预先感谢
答案 0 :(得分:1)
您还可以在after_server_stop
上注销对test_cli
的呼叫:
test_cli.server.after_server_stop = []
答案 1 :(得分:0)
我认为您可以通过更改以下方法来修复错误:
@app.listener('after_server_stop')
async def finish(app, loop):
await app.aiohttp_session.close()
这不是关闭循环的责任,您应使用从异步上下文调用finish
的事实(您无需启动事件循环,它已经在运行)。
如果这不是唯一的问题,那么也许从一个简单的示例开始,然后添加内容,直到它再次崩溃为止。 Sanic似乎有一个有据可查的test section,建议在其中使用pytest-sanic。一个使用pytest的简单示例如下所示:
# file: server.py
from sanic import Sanic
from sanic.response import json
app = Sanic()
@app.route('/')
async def test(request):
return json({'hello': 'world'})
if __name__ == "__main__":
app.run(host='0.0.0.0', port=8000)
# file: server_test.py
import pytest
from server import app
@pytest.yield_fixture
def sanic_app():
yield app
@pytest.fixture
def test_cli(loop, sanic_app, sanic_client):
return loop.run_until_complete(sanic_client(app))
async def test_index(test_cli):
resp = await test_cli.get('/')
assert resp.status == 200
json = await resp.json()
assert json == {'hello': 'world'}
async def test_index_fail(test_cli):
resp = await test_cli.get('/')
assert resp.status == 200
json = await resp.json()
assert json == {'bonjour': 'monde'}
您需要安装一些软件包:
pip install sanic pytest pytest-sanic
然后,您可以只运行pytest
,则应该使第一个测试通过,第二个失败。
通常,您不必自己启动事件循环,而总是尝试摆脱loop.run_...
。