前言
当编写测试时,有时需要模拟时间的流逝来测试与时间相关的功能。那如何解决该问题呢?带着疑问一起看。
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实现原理的简要说明:
- pytest-freezegun定义了一个名为
freezer
的自定义fixture。这个fixture使用@pytest.fixture
装饰器进行标记。 - 在fixture的实现中,pytest-freezegun使用freezegun库的
freeze_time
函数创建一个时间冻结器对象。并通过调用start
方法开始冻结时间。 - 冻结后的时间对象被返回给测试函数,测试函数可以使用这个对象来模拟和控制时间的流逝。
- 当测试函数执行完毕后,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,我们可以轻松地模拟时间,以便测试与时间相关的功能。通过冻结时间或模拟时间的流逝,我们可以编写准确、可靠的时间相关的测试。