如何为异步流服务器编写 pytest 夹具?

2024-04-10

我一直在尝试学习 asyncio,但找不到任何创建可用于测试服务器代码的 pytest 夹具的示例。一旦服务器启动,我猜它会阻止其他一切,因此测试永远不会运行。 pytest-asyncio 是否有办法在单独的线程中运行固定装置或其他东西?还是需要自己写线程代码?或者,还有更好的方法?下面是我一直在弄乱的一些代码。是官方直接复制粘贴的使用流的 TCP 回显服务器 https://docs.python.org/3/library/asyncio-stream.html#tcp-echo-server-using-streams带有 pytest 夹具的文档并在最后进行测试:

import asyncio
import pytest


async def handle_echo(reader, writer):
    data = await reader.read(100)
    message = data.decode()
    addr = writer.get_extra_info('peername')

    print(f"Received {message!r} from {addr!r}")

    print(f"Send: {message!r}")
    writer.write(data)
    await writer.drain()

    print("Close the connection")
    writer.close()


async def main():
    server = await asyncio.start_server(
        handle_echo, '127.0.0.1', 8888)

    addr = server.sockets[0].getsockname()
    print(f'Serving on {addr}')

    async with server:
        await server.serve_forever()


@pytest.fixture(scope="session")
async def server():
    return await main()


@pytest.mark.asyncio
def test_something(server):
    assert False

如果你特别想要session范围,您可能在与合作调度的服务器方面不走运pytest-asyncio。如果你愿意安于现状function范围,我已经让它发挥作用了。当然,这意味着您的服务器将在每次测试时启动和停止,这对于这里的微不足道的回显服务器来说并不是很多开销,但对于您的实际服务器来说可能是这样,无论它是什么。这是对您的示例的改编,对我有用。

HOST = "localhost"

@pytest.fixture()
def server(event_loop, unused_tcp_port):
    cancel_handle = asyncio.ensure_future(main(unused_tcp_port), loop=event_loop)
    event_loop.run_until_complete(asyncio.sleep(0.01))

    try:
        yield unused_tcp_port
    finally:
        cancel_handle.cancel()

async def handle_echo(reader, writer):
    data = await reader.read(100)

    message = data.decode()
    addr = writer.get_extra_info('peername')
    print(f"SERVER: Received {message!r} from {addr!r}")
    writer.write(data)
    await writer.drain()
    print(f"SERVER: Sent: {message!r}")

    writer.close()
    print("SERVER: Closed the connection")


async def main(port):
    server = await asyncio.start_server(handle_echo, HOST, port)

    addr = server.sockets[0].getsockname()
    print(f'SERVER: Serving on {addr[0:2]}')

    async with server:
        await server.serve_forever()


@pytest.mark.asyncio
async def test_something(server):
    message = "Foobar!"
    reader, writer = await asyncio.open_connection(HOST, server)

    print(f'CLIENT: Sent {message!r}')
    writer.write(message.encode())
    await writer.drain()

    data = await reader.read(100)
    print(f'CLIENT: Received {data.decode()!r}')

    print('CLIENT: Close the connection')
    writer.close()
    await writer.wait_closed()

精明的读者会注意到asyncio.sleep(0.01)在服务器固定装置中。我不知道非决定论是否是固有的asyncio实现,或者特定于 pytest 的使用,但没有那个sleep大约 20% 的情况下(当然,在我的机器上)服务器在测试尝试连接到服务器之前不会开始侦听,这意味着测试会失败ConnectionRefusedError。我玩了很多次......旋转事件循环一次(通过loop._run_once())不保证服务器会监听。睡觉为0.001s仍然有大约 1% 的时间失败。睡觉为0.01s似乎在 1,000 次运行中通过了 100%,但如果你想成为really当然,你会做这样的事情:

# Replace `event_loop.run_until_complete(asyncio.sleep(0.01))` with this:
event_loop.run_until_complete(asyncio.wait_for(_async_wait_for_server(event_loop, HOST, unused_tcp_port), 5.0))


async def _async_wait_for_server(event_loop, addr, port):
    while True:
        a_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        try:
            await event_loop.sock_connect(a_socket, (addr, port))
            return
        except ConnectionRefusedError:
            await asyncio.sleep(0.001)

        finally:
            a_socket.close()

在运行测试之前,它将继续尝试连接,直到成功(或者不太可能在 5 秒后超时)。这就是我在“真实”测试中的做法。

现在,关于范围。从源码来看,看起来像pytest-asyncio已决定event_loop是一个函数范围的固定装置。我尝试编写自己的模块/会话范围版本,但他们在内部使用它来在自己的事件循环上安排每个测试(大概是为了防止测试以某种方式相互干扰)。所以除非你想放弃pytest-asyncio并“推出您自己的”测试工具来将测试作为异步协程运行,我认为您在更大的范围上几乎不走运。

FWIW,在找到这个协作解决方案之前,我尝试了“后台线程”、模块范围的解决方案,这有点痛苦。首先,您的服务器需要一种方法来执行线程安全、干净的关闭,并且可以从本身在主线程上运行的固定装置触发。其次,(这对你来说可能并不重要,但对我来说确实如此)调试绝对令人抓狂。在单个操作系统线程中运行的单个事件循环上跟踪协程执行的(众所周知的)“线程”已经足够困难了。尝试跨两个线程解决这个问题,每个线程都有自己的事件循环,但只有一个线程在任何给定时间停止......好吧,这很困难。基本场景是这样的:我有一个包含一百个测试的文件。我运行它。约 50 次测试失败。这很奇怪,我只改变了一件小事......我可以在控制台输出中看到回溯,有些东西在服务器代码深处引发了异常。没问题,我会在那里放置一个断点。在调试器中再次运行。执行在断点处停止。伟大的!好的,现在,50 个测试中的哪一个触发了此错误?哦!我不知道,因为调试器中只有后台线程停止。我最终找出了错误,修复了它,再次运行,测试 100% 通过。啊?哦...是的...因为服务器在整个会话中运行,并且其内部状态被一个测试打乱,所以某些其他测试在打乱之后会失败。

长话短说,后台线程/更广泛范围的解决方案是可能的,但没有那么好。第二个教训是你实际上可能want每个测试/功能范围的服务器固定装置,以便您的测试彼此隔离。

顺便说一句:作为一个有点测试的书呆子,我什至在做这个的想法上挣扎(测试客户端和服务器端对端)pytest)。正如我在最初的评论中所说,此时它不再是真正的“单元测试”,而是“集成测试”,因此单元测试框架没有设置得很好也就不足为奇了开箱即用。幸运的是,对于我的所有疑虑,这样做已经帮助我找到(并修复)了可能到目前为止的十几个错误,我真的很高兴我可以在无头测试工具中找到/复制,而不是通过编写一堆selenium脚本或更糟糕的是,手动单击网页。由于服务器与单个线程中的测试协同运行,因此使用调试器变得非常容易。玩得开心!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何为异步流服务器编写 pytest 夹具? 的相关文章

随机推荐

  • 如何调试 STL/C++ 的 GCC/LD 链接过程

    我正在用 C 开发裸机 cortex M3 以获取乐趣和利润 我使用 STL 库 因为我需要一些容器 我认为通过简单地提供我的分配器 它不会向最终的二进制文件添加太多代码 因为您只得到您使用的内容 实际上我根本没想到与 STL 有任何链接过
  • 如何使用OSRM匹配服务

    如标题中所述 如何使用匹配调用 I tried http router project osrm org match v1 driving 8 610048 46 99917 8 530232 47 051 overview full ra
  • 如何从内联样式中获取百分比高度而不是像素高度?

    所以基本上 我有许多具有内联样式的元素height与百分比 虽然 当我尝试将高度保存为要使用的变量时 它会将其保存为像素 例如 div class wrapper div style height 10 Testing 123 div di
  • 更新到 MacOs Mojave 10.14 后是否有 OpenGL 黑屏和伽玛校正?

    我不小心更新了我的 Mac OpenGL 现在在最新的 MacOS Mojave 中已被弃用 我正在使用 OpenGL GLUT 我知道这很旧 但我只需要一个简单的程序 并在终端上运行 不使用 Xcode 使用在 Sierra 中完美运行的
  • 基于ONVIF wsdl生成Java webservice(自顶向下方法)

    我想为像相机这样的设备生成服务器代码 这些设备有一个标准协议 称为ONVIF https www onvif org它发布了一些流行的 WSDL 文档 所以我必须从 ONFIV 的 WSDL 文档生成一个接口和骨架 当我使用 wsdl2ja
  • Oracle 动态旋转

    我有下表 我需要根据 CCL 列创建列 CCL 列中的值未知 我不知道从哪里开始 任何帮助 将不胜感激 TABLEA ID CCL Flag 1 john x 1 adam x 1 terry 1 rob x 2 john x Query
  • Blackberry Java 中的类之间调用

    当屏幕上 单击 位图时 我试图推送一个新屏幕 为此 我从这篇文章中创建了一个类 黑莓可点击位图字段 https stackoverflow com questions 5722875 blackberry clickable bitmapf
  • 安装 gem 时出错:无法为 cygwin 的堆保留空间,Win32 错误 487

    我正在尝试安装win32 api我的机器上安装了 gem 并且在构建本机扩展时遇到了一些问题 gem install win32 api no ri rdoc Temporarily enhancing PATH to include De
  • 如何为WinForm、C#制作框架?

    我一直在研究改变Windows窗体边框的颜色 发现它是由Windows决定的 好吧 这是有道理的 所以我看到以前问过这个问题的人被告知去这里http customerborderform codeplex com http customer
  • 内存警告后 WKWebView 变为空白

    我正在开发一个 iOS 应用程序 它将在 wkWebView 中显示一些 360 度全景内容 该页面确实会加载 但当它收到内存警告时 它会在 iPad 2 上显示空白视图 相关代码 NSURLRequest req NSURLRequest
  • @selector 和其他类 (Objective-C)

    在对象内部我使用 NSMenu 的addItemWithTitle action keyEquivalent 创建 NSMenuItems 问题是我希望调用另一个对象上的方法作为操作 这action 部分需要一个 selector作为参数
  • pinterest 布局样式的 CSS 代码

    我的挑战是尝试使列表网格视图看起来像 pinterest 类似的布局 我已经用它编写了一些代码 但这还不够 下面的行彼此不匹配 content category grid view li featured position relative
  • Expressjs Passport-Local 无法注销

    我将应用程序 Passport local 复制粘贴到我的应用程序上 有趣的是我可以登录用户 但我不能让他们注销 app get logout function req res req logout res redirect 这并不是什么都
  • SQL Server Express 中的链接服务器

    我正在开发一个应用程序 其中我在 SQL Server Express 中有一个本地数据库 在本地数据库中工作期间 我们需要在另一个 SQL Server 实时服务器上执行查询 并返回一个值 并使用该值在本地服务器中执行查询 对 2 或 3
  • Angular 6:无法绑定到“formGroup”,因为它不是“form”的已知属性?

    我曾在 Angular 2 4 中使用过表单生成器 但现在我在 Angular 6 中使用它 我看到了这个问题 无法绑定到 formGroup 因为它不是 form 的已知属性 https stackoverflow com questio
  • 如何使用 ts 中的变量作为 HTML 文件中的标记名? [复制]

    这个问题在这里已经有答案了 我想知道是否有什么方法可以使用HTML tag name p for e g 这是从变量获得的 以下是我尝试过的代码 应用程序组件 ts import Component OnInit from angular
  • 在 Jupyter Notebook 中的 %matplotlib inline 之后使用 %matplotlib Notebook 不起作用

    我正在使用 Jupyter Notebook 来绘制饼图 In 第一个细胞用我的代码我有一个神奇的命令 matplotlib inline在这个神奇的命令之后 我运行我的代码 一切正常并且我的图形呈现 But in 第二个细胞当我设置 ma
  • Tensorboard 错误:当前数据集没有活动的仪表板

    我正在尝试使用 Tensorboard 但每次使用 Tensorflow 运行任何程序时 当我转到 localhost 6006 查看可视化时都会收到错误 这是我的代码 a tf add 1 2 b tf multiply a 3 with
  • getResponseHeader 不是函数

    我需要从另一个页面获取值 但我通过以下代码收到此错误 我该如何修复它 document ready function name submit click function ajax type POST data form signup se
  • 如何为异步流服务器编写 pytest 夹具?

    我一直在尝试学习 asyncio 但找不到任何创建可用于测试服务器代码的 pytest 夹具的示例 一旦服务器启动 我猜它会阻止其他一切 因此测试永远不会运行 pytest asyncio 是否有办法在单独的线程中运行固定装置或其他东西 还