简体   繁体   English

使用 pytest 测试 Flask 中的服务器发送事件

[英]Testing server-sent events in Flask with pytest

I have a Flask application where I use server-sent events to send data to my front-end.我有一个 Flask 应用程序,我使用服务器发送的事件将数据发送到我的前端。

@bp.route("/stream", methods=("GET",))
def stream_translations():
    translation_schema = TranslationSchema()

    def event_stream():
        while True:
            recently_updated = [
                translation_schema.dump(translation)
                for translation in recently_updated_translations()]
            if recently_updated:
                yield f"data: {json.dumps(recently_updated)}\n\n"

    return Response(event_stream(), mimetype="text/event-stream")

It works fine, but I also want to write a test for it to make sure.它工作正常,但我也想为它写一个测试以确保。 I've never written a test for a generator before, and definitely not server-sent events.我以前从未为生成器编写过测试,绝对不是服务器发送的事件。 Currently this is what I have:目前这就是我所拥有的:

def test_stream(client):
    response = client.get("/translations/stream")

    assert response.status_code == 200
    assert response.mimetype == "text/event-stream"

Of course this just tests the response, but I also want to test the event_stream() generator.当然这只是测试响应,但我也想测试event_stream()生成器。 How do I do this?我该怎么做呢?

With some minor refactoring, we can successfully test our SSE endpoints through careful test mocks.通过一些小的重构,我们可以通过仔细的测试模拟成功地测试我们的 SSE 端点。

Step 1. Adding a "Bypass" for the Infinite Loop步骤 1. 为无限循环添加“旁路”

The first issue that you may be facing when testing this code is that SSE event streams promotes using while True loops.测试此代码时您可能面临的第一个问题是 SSE 事件流促进使用while True循环。 This makes sense in a browser environment, but not so much in a server-side integration test, since it will cause your test cases to "hang".这在浏览器环境中是有意义的,但在服务器端集成测试中则没有意义,因为它会导致您的测试用例“挂起”。

We can address this by refactoring your generator code into a helper function:我们可以通过将生成器代码重构为帮助程序 function 来解决此问题:

import time

def event_stream(timeout = 0.0):
    starting_time = time.time()
    while not timeout or (time.time() - starting_time < timeout):
        ...
        yield f'data:{json.dumps(...)}'

With this, you can refactor your original stream_translations function as such:有了这个,你可以重构你原来的stream_translations function:

@bp.route("/stream", methods=("GET",))
def stream_translations():
    return Response(event_stream(), mimetype='text/event-stream')

With this refactor, we accomplish two things:通过这个重构,我们完成了两件事:

  1. event_stream has an additional timeout parameter, which allows us to prevent the infinite loop in tests event_stream有一个额外的timeout参数,它允许我们防止测试中的无限循环

  2. event_stream is not a nested function, which allows us to mock it appropriately. event_stream不是嵌套的 function,它允许我们适当地模拟它。

Step 2. Bypassing the Infinite Loop in Tests步骤 2. 绕过测试中的无限循环

Ideally, we want to only specify the timeout parameter in tests, but allow it to continue forever in production settings.理想情况下,我们只想在测试中指定timeout参数,但允许它在生产设置中永远持续下去。 We can accomplish this through mocks:我们可以通过 mocks 来实现:

from contextlib import contextmanager
from functools import partial
from unittest import mock

@contextmanager
def mock_events():
    with mock.patch(
        # NOTE: For example, if your function is found in app/views/translations.py,
        # then this import path would be 'app.views.translations.event_stream'
        'python.import.path.to.stream_translations.event_stream',

        # NOTE: Remember to import this function wherever you defined it.
        partial(event_stream, timeout=1.5),
    ):
        yield

This allows us to minimize differences between test code and production code (since the same function is run), but actually allows this function to complete in tests.这允许我们最小化测试代码和生产代码之间的差异(因为运行相同的 function),但实际上允许这个 function 在测试中完成。

With this function, your test may look something like this:使用此 function,您的测试可能如下所示:

def test_stream(client):
    with mock_events():
        response = client.get("/translations/stream")

    assert response.data.decode()

After a while my friend solve it very easy using pytest :过了一会儿,我的朋友使用pytest很容易解决它:

@fixture(scope='function')
def stream_start(client: FlaskClient):
    client.post("/stream/start")
    sleep(4)  # you get data for 4 sec
    client.post("/stream/stop")

def test_output_stream(client: FlaskClient, api_start):  
    with client.get('/stream/output_stream') as res:
        for encoded in res.iter_encoded():
            if encoded:
                data = json.loads(encoded.decode('UTF-8')[::])
                assert real_data == data 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 使用Python,Twisted和Flask的服务器发送事件:这是一种正确的睡眠方法吗? - Server-sent events with Python,Twisted and Flask: is this a correct approach for sleeping? 如何在 Flask 框架中实现 Server-Sent Events? - How to implement Server-Sent Events in Flask framework? 2016年Django服务器发送的事件 - Django server-sent events in 2016 如何在 Flask Server-Sent Events 的生成器中为睡眠功能配置 gunicorn 和 gevent? - How to configure gunicorn and gevent for sleep function inside a generator for Flask Server-Sent Events? 使用 pytest 测试 flask-socketio 服务器发出的事件时出错 - Error testing flask-socketio server emitted events with pytest 在CGIHTTPServer上使用Python cgi脚本发送的HTML5 Server发送事件 - HTML5 Server-sent events with Python cgi script on CGIHTTPServer Firefox 不恢复服务器发送的事件连接 - Firefox doesn't restore server-sent events connection 检测客户端何时与Heroku上的Django和服务器发送的事件断开连接 - Detecting when a client disconnects with Django and Server-Sent Events on Heroku 是否可以将gzip压缩与服务器发送事件(SSE)一起使用? - Is it possible to use gzip compression with Server-Sent Events (SSE)? 服务器使用Flask和Tornado发送事件 - Server sent events with Flask and Tornado
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM