Ste*_*ven 3 python unit-testing patch freezegun python-dataclasses
我试图在我的单元测试中使用 freezegun 来修补数据类中的字段,该字段设置为对象初始化时的当前日期。我想这个问题与任何修补被用作 default_factory 的函数的尝试有关,只是在 freezegun 之外。数据类被冻结,所以它是不可变的。
例如,如果我的数据类是:
@dataclass(frozen=True)
class MyClass:
name: str
timestamp: datetime.datetime = field(init=False, default_factory=datetime.datetime.now)
Run Code Online (Sandbox Code Playgroud)
当我使用 freezegun 修补 datetime 时,它对 MyClass 中时间戳的初始化没有影响(它仍然将时间戳设置为单元测试中 now() 返回的当前日期,导致测试失败)。
我假设它与在补丁到位之前加载的默认工厂和模块有关。我尝试修补日期时间,然后使用 importlib.reload 重新加载模块,但没有运气。
我目前的解决方案是:
@dataclass(frozen=True)
class MyClass:
name: str
timestamp: datetime.datetime = field(init=False)
def __post_init__(self):
object.__setattr__(self, "timestamp", datetime.datetime.now())
Run Code Online (Sandbox Code Playgroud)
哪个有效。
理想情况下,我想要一个非侵入性的解决方案,它不需要我更改我的生产代码来启用我的单元测试。
你是对的,数据类的创建过程在这里做了一些奇怪的事情,这导致了你当前的问题。它在类创建期间绑定工厂函数,这意味着它在 freezegun 有机会修补它之前持有代码的引用。
这是一个没有数据类的例子,它遇到了同样的问题:
from datetime import datetime
from freezegun import freeze_time
class Foo:
# looks up the function at class creation time
now_func = datetime.now
def __init__(self):
# asks datetime for a reference at instance creation time
self.timestamp_a = datetime.now()
# uses an old reference we couldn't patch
self.timestamp_b = Foo.now_func()
with freeze_time(datetime(2020, 1, 1)):
foo = Foo()
assert foo.timestamp_a == datetime(2020, 1, 1) # works
assert foo.timestamp_b == datetime(2020, 1, 1) # raises an AssertionError
Run Code Online (Sandbox Code Playgroud)
至于如何解决这个问题,理论上你可以MyClass.__init__.__closure__在测试过程中进行hack来切换功能,但这有点疯狂。
比timestamp在 a 中覆盖更好的事情__post_init__可能是仅使用 lambda 委托函数调用,以便名称查找延迟到实例化时间:
timestamp: datetime = field(init=False, default_factory=lambda: datetime.now())
Run Code Online (Sandbox Code Playgroud)
或者,您可以开始使用不同的日期时间库,如支持开箱即用的冻结时间的 pendulum 。FWIW,这就是我最终要做的。