我有一些现有的 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()
可以依赖于一个对象。或者它可以成为一个物体。我们可以创建两个对象,一个返回当前日期时间,另一个返回用于测试的固定值。我不知道如何将其注入 FastAPIapp
,或者即使这是可能的。这将是我最喜欢的方法,因为这是我最熟悉的方法。 -
使用环境变量来更改
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