首页 > 编程问答 >如何在 FastAPI 测试中处理 datetime.now() 等外部依赖项?

如何在 FastAPI 测试中处理 datetime.now() 等外部依赖项?

时间:2024-07-21 06:33:33浏览次数:16  
标签:python python-3.x fastapi

我有一些现有的 FastAPI 测试不再通过,因为一些内部服务器逻辑已经更改,现在依赖于 datetime.now() 返回的值。

这是一个外部依赖项。通常,为了处理外部依赖项,我们会编写外部依赖项的模拟实现,并找到一种方法将其注入到正在测试的代码中。

我不确定这是否是像日期时间依赖项这样简单的事情的最佳方法。可能是,也可能不是。

下面是一些说明问题的 MWE 代码:

# lib_datetime.py

from datetime import datetime
from datetime import timezone

def now() -> datetime:
    return datetime.now(timezone.utc)
# fastapi_webserver.py

from fastapi import FastAPI

from lib_datetime import now

from datetime import datetime

app = FastAPI()

@app.get('/datetime_now')
def datetime_now():
    the_datetime_now = now()
    the_datetime_now_str = the_datetime_now.strftime('%Y-%m-%d %H:%M:%S.%f %z')
    return {
        'datetime_now': the_datetime_now_str,
    }
# test_fastapi_webserver.py

from fastapi.testclient import TestClient

from fastapi_webserver import app

from datetime import datetime
from datetime import timezone

client = TestClient(app)

def test_fastapi_webserver_datetime_now():

    datetime_now = datetime(year=2024, month=7, day=20, tzinfo=timezone.utc)
    datetime_now_str = datetime_now.strftime('%Y-%m-%d %H:%M:%S.%f %z')

    response = client.get('/datetime_now')
    print(response.json())
    assert response.status_code == 200
    assert response.json() == {
        'datetime_now', datetime_now_str,
    }

问题应该相当明显。返回的日期时间值根据测试运行的时间而变化。不好。

以下是我尝试修复它的摘要:

  • Monkeypatching。我尝试更改测试文件中的 lib_datetime.now() 指向的内容。我不知道如何完成这项工作。
  • 提升外部依赖项。理论上, now() 可以依赖于一个对象。或者它可以成为一个物体。我们可以创建两个对象,一个返回当前日期时间,另一个返回用于测试的固定值。我不知道如何将其注入 FastAPI app ,或者即使这是可能的。这将是我最喜欢的方法,因为这是我最熟悉的方法。
  • 使用环境变量来更改 now() 的运行时行为。同样,我不确定如何将其集成到测试代码中,并且我不确定这是否真的是一个好方法。

这里的障碍是我对 FastAPI 不太熟悉,因为我最近才开始使用它。所以我还不知道我能用它做什么。


提出的三种方法都适用于处理 FastAPI 测试中的 datetime.now() 依赖。下面我们详细介绍每种方法,并说明如何在的场景中实现它们:

1. Monkey Patching

  • 优点: 简单直接,不需要更改生产代码。
  • 缺点: 如果不仔细,可能会影响其他测试或使测试更难理解。

可以使用 unittest.mock 库中的 patch 装饰器在测试期间替换 lib_datetime.now 函数。

from unittest.mock import patch

from fastapi.testclient import TestClient

from fastapi_webserver import app
from lib_datetime import now

client = TestClient(app)

@patch('lib_datetime.now')
def test_fastapi_webserver_datetime_now(mock_now):
    mock_datetime = datetime(year=2024, month=7, day=20, tzinfo=timezone.utc)
    mock_now.return_value = mock_datetime

    response = client.get('/datetime_now')
    assert response.status_code == 200
    assert response.json() == {'datetime_now': mock_datetime.strftime('%Y-%m-%d %H:%M:%S.%f %z')}

2. 依赖注入

  • 优点: 更清晰的依赖关系,更容易测试不同的场景。
  • 缺点: 需要修改生产代码。

可以将 now 函数作为依赖项传递给的路径操作函数。

from fastapi import FastAPI, Depends
from datetime import datetime, timezone

from lib_datetime import now as get_current_datetime

app = FastAPI()

async def datetime_now(now=Depends(get_current_datetime)):
    the_datetime_now = now()
    the_datetime_now_str = the_datetime_now.strftime('%Y-%m-%d %H:%M:%S.%f %z')
    return {
        'datetime_now': the_datetime_now_str,
    }

# 测试
@patch('lib_datetime.now')
def test_fastapi_webserver_datetime_now(mock_now):
    mock_datetime = datetime(year=2024, month=7, day=20, tzinfo=timezone.utc)
    mock_now.return_value = mock_datetime

    # 使用 FastAPI 的依赖注入机制
    with app.container.now.override(mock_now):
        response = client.get('/datetime_now')

    assert response.status_code == 200
    assert response.json() == {'datetime_now': mock_datetime.strftime('%Y-%m-%d %H:%M:%S.%f %z')}

3. 环境变量

  • 优点: 可以在不同的环境中轻松配置应用程序的行为。
  • 缺点: 可能会使代码更难理解,并且可能不适用于所有情况。

可以在 lib_datetime.now 函数中检查环境变量,并在测试期间设置该变量。

import os
from datetime import datetime, timezone

def now() -> datetime:
    if os.getenv('USE_MOCK_DATETIME', 'False').lower() == 'true':
        return datetime(year=2024, month=7, day=20, tzinfo=timezone.utc)
    return datetime.now(timezone.utc)

# 测试
def test_fastapi_webserver_datetime_now():
    with patch.dict(os.environ, {"USE_MOCK_DATETIME": "True"}):
        response = client.get('/datetime_now')

    mock_datetime = datetime(year=2024, month=7, day=20, tzinfo=timezone.utc)
    assert response.status_code == 200
    assert response.json() == {'datetime_now': mock_datetime.strftime('%Y-%m-%d %H:%M:%S.%f %z')}

推荐方法:

对于的情况, 依赖注入 可能是最干净和最灵活的解决方案。它允许在不修改 lib_datetime 模块本身的情况下轻松地用模拟实现替换 now 函数。这使得的测试更易于理解和维护。

请记住,最佳方法取决于的具体需求和编码风格。

标签:python,python-3.x,fastapi
From: 78773843

相关文章

  • Python Pandas - 读取 CSV 或 Excel
    我允许用户上传CSV或Excel文件。我正在使用pandas读取文件并创建数据框。由于我无法预测用户将上传哪种文件类型,因此我将pd.read_csv()和pd.read_excel()包装在try/except块中。ifform.validate_on_submit():input_filename=secure_filename(form.file.da......
  • 如何在 python 脚本中调用 robocopy 来批量复制多个文件夹?
    我正在尝试在网络驱动器之间移动多个大文件夹(>10Gb、>100个子文件夹、>2000个文件)。我尝试过在python中使用Shutil.copytree命令,它工作得很好,只是由于不同的原因它无法复制一小部分(<1%的文件)。我相信robocopy对我来说是最好的选择,因为我可以创建一个记录传输过......
  • 如何在Python中使用装饰器动态创建类方法?
    我正在开发一个Python项目,我需要在运行时动态地为类创建方法。我想使用装饰器根据一些外部配置将这些方法添加到类中。要求是:装饰器应该从外部配置(例如字典)读取方法定义。装饰器应该动态地将这些方法添加到类中。每个生成的方法都应具有配置中指定的自己唯一的实现。以......
  • python 中的可扩展视频文件完整性验证
    我的目录包含约100万个视频文件,嵌套在100个子目录下。我想编写一个python脚本来验证这些文件没有损坏,然后删除损坏的文件。做到这一点最有效的方法是什么?它运行的机器有64个cpu核心。当然,以下是如何在Python中构建可扩展视频文件完整性验证器的步骤,该验证器可以......
  • Python:对很高维的矩阵进行对角化?
    目前我正在研究一个涉及对角化矩阵以获得特征值和特征向量的问题。但现在我想将问题扩展到200,000x200,000的尺寸。我查找了如何将矩阵存储在numpy中,有人建议使用PyTables。看起来很有希望。但我想知道哪里有工具可以帮助对PyTables中的矩阵存储进行对角化。......
  • 除了curses之外,是否有一个python包可以轻松控制终端的输出?
    我现在正在处理一些小项目,我对GUI的偏好是终端中漂亮的文本界面。我宁愿不强迫用户处理Windowscurses二进制文件,所以我正在寻找不同的选项。我已经发现了asciimatics,但我想考虑所有可能的选择。如果有人有任何经验或知道解决此用例的包,我将不胜感激。谢谢你说的没错......
  • 当值来自函数 python unittest 时,如何模拟全局变量
    我必须在python中模拟全局变量,但变量值来自另一个函数。当我导入文件时,这个函数正在运行,但我想要那里的模拟值。secrets.pyimporttracebackimportloggingimportboto3importosimportjsonlogger=logging.getLogger()logger.setLevel(logging.INFO)secret_......
  • 使用 python print 和 gdb 时出现 BrokenPipeError
    我正在尝试在Linux中运行应用程序并使用Python生成输入:python3-c'print(".....")'|./someapp但出现下一个错误:Exceptionignoredin:<_io.TextIOWrappername='<stdout>'mode='w'encoding='utf-8'>BrokenPipeError:......
  • python 舰队容器
    我正在尝试使用容器在flet中制作一个菜单,它应该是半透明的,但其中的项目不是。我尝试将opacity=1分配给元素,但没有成功-它们与容器一样透明感谢任何帮助我的代码:nickname=ft.TextField(label="xxx",hint_text="xxx")column=ft.Column(controls=[nickname......
  • Python应用程序跨子包共享的配置文件
    我正在构建一个应用程序来控制一些硬件。我在包中实现了不同类型的硬件:电机和测量设备。我的文件结构如下:name_of_my_app/__init__.pymain.pyconfig.iniCONFIG.pymotors/__init__.pyone_kind_of_motor.pymeasurement_devices/......