测试时间敏感功能的利器:深入理解 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,我们可以轻松地模拟时间,以便测试与时间相关的功能。通过冻结时间或模拟时间的流逝,我们可以编写准确、可靠的时间相关的测试。

相关推荐
2601_962293241 小时前
Python自动化统计团队工作量并生成可视化仪表盘的脚本方案【指导】
python·数据分析·自动化·可视化·仪表盘
摇滚侠1 小时前
《SpringBoot 3:入门与应用实战》第 12 章 JDBC 与事务 使用 JdbcTemplate 阅读笔记 32
spring boot·笔记·后端
2601_962293792 小时前
OCRmyPDF批处理脚本:使用Python自动化复杂OCR任务
python·自动化·批处理脚本·ocrmypdf·ocr任务
步行cgn2 小时前
@Configuration 详解:Spring 配置类的核心注解
java·后端·spring
Java后端的Ai之路3 小时前
20、Python - 备忘录模式
开发语言·人工智能·python·外观模式·备忘录模式
2601_962077714 小时前
基于Python与NLP的新闻事件信息抽取实战:从NER到时空标准化
python·nlp·transformers·spacy·新闻事件抽取
桦说编程4 小时前
【AtomicAgent系列1】变异测试——过去做不起,现在 agent 做得起
后端·ai编程·vibecoding
JavaPub-rodert4 小时前
LangChain 从入门到 Agent 实战:用 Python 搭建一个真正能调用工具和知识库的 AI 助手
人工智能·python·langchain
郝学胜-神的一滴5 小时前
Qt 高级编程 045:坐标体系深度实战
开发语言·c++·windows·python·qt·程序人生
考虑考虑5 小时前
nginx打印请求日志
运维·后端·nginx