测试时间敏感功能的利器:深入理解 pytest-freezegun

前言

当编写测试时,有时需要模拟时间的流逝来测试与时间相关的功能。那如何解决该问题呢?带着疑问一起看。

pytest-freezegun是啥?

先抛出答案,使用这个插件可以轻松解决问题。

pytest-freezegun是一个非常有用的工具,它可以帮助我们在测试中冻结时间。这个插件随时可以变化当前系统时间,freezer可以冰冻时间,freezer.move_to可以改变时间,解决验证某一时间点的代码触发,或未来时间的代码变化问题。

安装 pytest-freezegun

首先,我们需要安装pytest-freezegun。

复制代码
pip install pytest-freezegun

使用 pytest-freezegun

方法一:使用mark标记

pytest-freezegun允许我们在测试中冻结时间,以便于测试与时间相关的功能。它通过在测试函数上应用装饰器来实现这一点。下面是一个简单的示例:

python 复制代码
import datetime
import pytest
​
​
@pytest.mark.freeze_time("2023-11-15")
def test_current_date():
    current_date = datetime.datetime.now()
    assert current_date == datetime.datetime(2023, 11, 15)

在上述示例中,我们使用@pytest.mark.freeze_time("2023-11-15")冻结在指定的时间点("2023-11-15"),而实际上当前时间是"2023-11-16"。在冻结时间期间,datetime.datetime.now()将始终返回指定的时间,这样我们就可以测试基于时间的逻辑。

执行这条用例,你会发现是通过的。

方法二:使用freezer fixture

ini 复制代码
import datetime
​
​
def test_current_date(freezer):
    current_date = datetime.datetime.now()
    freezer.move_to("2013-11-15")
    late = datetime.datetime.now()
    assert current_date == late

执行结果如下:

scss 复制代码
assert FakeDatetime(2023, 11, 16, 9, 20, 22, 606530) == FakeDatetime(2013, 11, 15, 0, 0)

代码中,我们使用freezer.move_to来改变时间。

模拟时间的流逝

除了在特定时间点冻结时间外,pytest-freezegun还允许我们模拟时间的流逝。我们可以通过向@freeze_time装饰器传递一个字符串参数来指定时间的增量。下面是一个示例:

python 复制代码
import datetime
import pytest
​
@pytest.mark.freeze_time("2023-11-15")
def test_current_date():
    current_date = datetime.datetime.now()
    new_date = current_date + datetime.timedelta(days=5)
    assert new_date == datetime.datetime(2023, 11, 20)

在上述示例中,我们使用datetime.timedelta类来表示时间的增量。通过将增量传递给datetime.timedelta,我们可以模拟时间的流逝。

运行原理

pytest-freezegun是一个Pytest插件,它基于freezegun库实现了对时间冻结的支持。

pytest-freezegun插件通过使用Pytest的fixture机制,为测试函数提供了方便的时间冻结功能。下面是pytest-freezegun实现原理的简要说明:

  1. pytest-freezegun定义了一个名为freezer的自定义fixture。这个fixture使用@pytest.fixture装饰器进行标记。
  2. 在fixture的实现中,pytest-freezegun使用freezegun库的freeze_time函数创建一个时间冻结器对象。并通过调用start方法开始冻结时间。
  3. 冻结后的时间对象被返回给测试函数,测试函数可以使用这个对象来模拟和控制时间的流逝。
  4. 当测试函数执行完毕后,pytest会自动清理fixture,并调用冻结器对象的stop方法停止时间冻结。

通过以上步骤,pytest-freezegun插件实现了Pytest中对时间冻结的支持。

看看源码

ini 复制代码
@pytest.fixture(name=FIXTURE_NAME)
def freezer_fixture(request):
    """
    Freeze time and make it available to the test
    """
    args = []
    kwargs = {}
    ignore = []
​
    # If we've got a marker, use the arguments provided there
    marker = get_closest_marker(request.node, MARKER_NAME)
    if marker:
        ignore = marker.kwargs.pop('ignore', [])
        args = marker.args
        kwargs = marker.kwargs
​
    # Always want to ignore _pytest
    ignore.append('_pytest.terminal')
    ignore.append('_pytest.runner')
​
    # Freeze time around the test
    freezer = freeze_time(*args, ignore=ignore, **kwargs)
    frozen_time = freezer.start()
    yield frozen_time
    freezer.stop()
​

解释一下代码的作用:

首先,通过检查测试函数上是否有特定标记(MARKER_NAME)来判断是否应用了时间冻结。如果有标记,则从标记中获取参数,包括忽略的模块列表(ignore)、位置参数和关键字参数。

接下来,将一些固定的模块(例如_pytest.terminal_pytest.runner)添加到忽略列表中,以确保这些模块不会受到时间冻结的影响。

然后,使用freeze_time函数创建一个时间冻结器对象(freezer),并调用其start方法开始冻结时间。冻结后的时间对象(frozen_time)被返回给测试函数使用。

在测试函数执行完毕后,使用yield语句将冻结后的时间对象返回给测试函数。这样,测试函数就可以在其运行期间使用冻结后的时间对象。

最后,使用stop方法停止时间冻结,以便时间可以继续流动。

最后

使用pytest-freezegun,我们可以轻松地模拟时间,以便测试与时间相关的功能。通过冻结时间或模拟时间的流逝,我们可以编写准确、可靠的时间相关的测试。

相关推荐
2301_79658850几秒前
Redis怎样优化大量Lua并发调用带来的CPU压力
jvm·数据库·python
张小洛2 分钟前
Spring 常用类深度剖析(工具篇 04):CollectionUtils 与 Stream API 的对比与融合
java·后端·spring·spring工具类·spring utils·spring 类解析
l1t2 分钟前
利用python statsmodels包分析数据
开发语言·python
2301_796588503 分钟前
如何阻止 HTML 页面在 JavaScript 脚本执行完成前渲染
jvm·数据库·python
DeepModel5 分钟前
通俗易懂讲透 EM 算法(期望最大化)
人工智能·python·算法·机器学习
海海不掉头发6 分钟前
【AI大模型实战项目】大模型入门实战:两个落地项目保姆级教程12月14日-【项目】基于知识库RAG的物流行业信息问答系统
人工智能·python·深度学习·语言模型·自然语言处理·pycharm·scikit-learn
2301_773553626 分钟前
mysql执行SQL查询时结果不一致_检查事务隔离级别设置与幻读
jvm·数据库·python
m0_377618238 分钟前
mysql如何解决乱码问题_检查客户端与服务器字符集一致性
jvm·数据库·python
m0_747854529 分钟前
html怎么转astro island模式_Astro Islands如何隔离HTML组件
jvm·数据库·python
m0_7489203613 分钟前
如何利用SQL触发器自动记录数据修改_编写审计日志逻辑
jvm·数据库·python