【问题标题】:Python Dataclasses: Mocking the default factory in a frozen DataclassPython Dataclasses:在冻结的 Dataclass 中模拟默认工厂
【发布时间】:2020-07-30 03:07:44
【问题描述】:

我试图在我的单元测试中使用 freezegun 来修补数据类中的一个字段,该字段设置为对象初始化时的当前日期。我想这个问题与任何尝试修补一个在 freezegun 之外用作 default_factory 的函数有关。数据类被冻结,因此它是不可变的。

例如,如果我的数据类是:

@dataclass(frozen=True)
class MyClass:
    name: str
    timestamp: datetime.datetime = field(init=False, default_factory=datetime.datetime.now)

当我用 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())

有效。

不过,理想情况下,我想要一个不需要我更改生产代码来启用单元测试的非侵入性解决方案。

【问题讨论】:

    标签: python unit-testing patch python-dataclasses freezegun


    【解决方案1】:

    你是对的,数据类创建过程在这里做了一些奇怪的事情,这导致了你当前的问题。它在类创建期间绑定工厂函数,这意味着它在 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
    

    至于如何解决这个问题,理论上你可以在测试期间破解MyClass.__init__.__closure__ 来切换功能,但这有点疯狂。

    比在 __post_init__ 中覆盖 timestamp 更好的方法可能是仅使用 lambda 委托函数调用,以便将名称查找延迟到实例化时间:

    timestamp: datetime = field(init=False, default_factory=lambda: datetime.now())
    

    或者您可以开始使用不同的日期时间库,例如 pendulum,它支持冻结时间out of the box。 FWIW,这就是我最终要做的。

    【讨论】:

    • 谢谢。回想起来,lambda 似乎如此明显,我不敢相信我错过了那个选项。特别是因为我一直在使用 FactoryBoy,它出于非常相似的原因广泛使用 lambda。我会研究一下 pendulum 库,因为 python 的 datetime 库不是很好。当需要模拟其他类型的 default_factory 时,lambda 解决方案是一个很好的选择。
    • 很高兴我能帮上忙 =)
    猜你喜欢
    • 2018-05-04
    • 1970-01-01
    • 1970-01-01
    • 2017-01-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-14
    相关资源
    最近更新 更多