模拟无法阻止昂贵的资源

问题描述 投票:0回答:1

在下面的最小代码示例中,我展示了两个相同的类。两个版本都可以在提取此摘录的原始源中正常工作。

问题是一条通常有效的 tkinter 错误消息,但似乎是错误生成的。请注意,有很多关于此的问题,所有这些问题都与错误消息的正确产生有关。

其中一个班级

Factory
总是在测试中失败。另一个
PostInit
有两个测试;一个有效,另一个失败。这些测试是为 pytest 框架编写的。

失败是tkinter错误:

运行时错误:创建变量太早:没有默认根窗口

通过的测试

test_post_init
嘲笑并阻止了整个
tkinter
。当在
test_tk_unmocked
中注释掉模拟时,会产生 tkinter 错误。我认为这证明了错误消息的正确产生。

在总是失败的情况下

test_factory
tkinter 再次被完全嘲笑,但仍然设法从 Tk/Tcl 内部深处产生错误消息。

test_factory
如何设法绕过
tkinter
行中对
monkeypatch.setattr(patterns_so, "tk", MagicMock())
的嘲笑?

"""patterns_so.py"""

from dataclasses import dataclass, field
import tkinter as tk


@dataclass
class PostInit:
    # Passes if tkinter is mocked.
    textvariable: tk.StringVar = None

    def __post_init__(self):
        self.textvariable = tk.StringVar()


@dataclass
class Factory:
    # Always fails.
    _textvariable: tk.StringVar = field(
        default_factory=tk.StringVar, init=False, repr=False
    )

"""test_patterns_so.py"""

from unittest.mock import MagicMock

import patterns_so


class TestFacade:
    def test_post_init(self, monkeypatch):
        # Passes.
        monkeypatch.setattr(patterns_so, "tk", MagicMock())
        patterns_so.PostInit()

    def test_tk_unmocked(self, monkeypatch):
        # Expected fail.
        # monkeypatch.setattr(patterns_so, "tk", MagicMock())
        patterns_so.PostInit()

    def test_factory(self, monkeypatch):
        # Unexpected fail.
        monkeypatch.setattr(patterns_so, "tk", MagicMock())
        patterns_so.Factory()

后记

根本问题似乎是数据类模块的机制过早实例化。

dataclasses
上文档†的底部附近有一条注释,指出当存在 default_factory 时,
init=False
将被忽略,因此它will 会包含在生成的
__init__
中。我通过进一步测试证实了这一点。

天真的我以为

__init__
仅在实例化类时运行。 根据文档
__init__
调用
__post_init__
。我不敢苟同。这里观察到的行为表明,生成的
__init__
函数实际上是在创建类时运行的 而不是稍后 在实例化类时运行。实例化后,
__post_init__
函数将运行。

如果模拟旨在阻止启动昂贵或持久的资源,它将失败:资源将被启动。

任何强制延迟模拟属性实例化的方法都可以。 Riccardo Bucco 的 lambda 或我使用

__post_init__
的示例都可以确保成功。

感谢 Riccardo Bucco 指导我对数据类有了更深入的理解。

†文档底部:又名“小字”。

python mocking python-dataclasses
1个回答
1
投票

您应该确保

tk.StringVar
不会在类定义时调用,而是在仅在模拟后执行的方法或函数中调用。一种解决方案是使用 lambda 或其他函数来延迟
tk.StringVar
的计算:

@dataclass
class Factory:
    _textvariable: tk.StringVar = field(
        default_factory=lambda: tk.StringVar(), init=False, repr=False
    )
© www.soinside.com 2019 - 2024. All rights reserved.