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

相关推荐
一点媛艺3 小时前
Kotlin函数由易到难
开发语言·python·kotlin
魔道不误砍柴功4 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
NiNg_1_2344 小时前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
_.Switch4 小时前
高级Python自动化运维:容器安全与网络策略的深度解析
运维·网络·python·安全·自动化·devops
Chrikk5 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*5 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue5 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man5 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang
测开小菜鸟5 小时前
使用python向钉钉群聊发送消息
java·python·钉钉
萧鼎7 小时前
Python并发编程库:Asyncio的异步编程实战
开发语言·数据库·python·异步