如何模拟单例 class 方法

How to mock a singleton class method

假设我们有以下结构:

class A():
    class __A():
        def __to_be_mocked(self):
            #something here
    def __init__(self):
        with A.lock:
            if not A.instance:
                A.instance = A.__A()

    def __getattr__(self,name):
        return getattr(self.instance,name)

现在我们要模拟函数__to_be_mocked。我们如何模拟它,因为mock.patch.object接受的目标是package.module.ClassName。我已经尝试了所有方法,如

target = A.__A
target = A.___A

还有很多。

编辑:

我用

解决了
target=A._A__A and attribute as '_A__to_be_mocked`

现在的问题是 __to_be_mocked__A 里面所以不应该是 ___A__to_be_mocked .

是因为A中的setattribute还是A中的__init__

Class & 以双下划线开头的实例成员重写了它们的名称,以防止与父 class 中的同名成员发生冲突,使它们的行为与 "private" 相同。所以这里的 __B 实际上可以作为 A._A__B 访问。 (下划线,class 名称,双下划线成员名称)。请注意,如果您使用单下划线约定 (_B),则不会发生重写。

话虽如此,您很少会看到有人实际使用这种形式的访问,并且 尤其是 不在产品代码中,因为 "private" 出于某种原因。如果没有更好的方法,也许是为了嘲笑。

我在 python 中嘲笑了很多东西,做了很多次之后我可以说:

  1. 从不 mock/patch __something 属性(又称 私有 属性)
  2. 避免到mock/patch_something属性(又称受保护属性)

私人

如果你模拟私人事物,你会混淆生产和测试代码。当你做这种模拟时,总有一种方法可以通过修补或模拟 public 或受保护的东西来获得相同的行为。

为了更好地解释我所说的混淆生产代码和测试代码的意思,我可以使用您的示例:修补 A.__B.__to_be_mocked()(我将 __A 内部 class 替换为 __B为了更清楚)你需要写一些像

patch('amodule.A._A__B._B__to_be_mocked')

现在,通过修补 __to_be_mocked,您正在测试中传播 ABto_be_mocked 名称:这正是我所说的混乱代码。因此,如果您需要更改某个名称,您应该进行所有测试并更改您的补丁,并且没有重构工具可以建议您更改 _A__B._B 字符串。

现在,如果你是一个好人并且把你的测试搞得一清二楚,那么你可以只在几个点上出现这些名字,但如果它是一个单身人士,我敢打赌它会像蘑菇一样被发现。

我想指出 private 和 protected 与某些安全问题无关,它们只是让您的代码更清晰的一种方式。这一点在 python 中是 crystal 清楚的,你不需要成为黑客来更改私有或受保护的属性:这些约定在这里只是为了帮助你阅读代码,你可以说 哦太棒了!我不需要了解它是什么......它只是肮脏的工作。恕我直言,python 中的私有属性无法实现此目标(__ 太长,看到它真的很困扰我)并且受保护就足够了。

旁注:理解 python 私有命名的小例子:

>>> class A():
...  class __B():
...   def __c(self):
...    pass
... 
>>> a = A()
>>> dir(a)
['_A__B', '__doc__', '__module__']
>>> dir(a._A__B)
['_B__c', '__doc__', '__module__']

回到你的案例:你的代码如何使用 __to_be_mocked() 方法?是否可以通过 patch/mock A 中的其他内容(而不是 A.__A)产生相同的效果 class?

最后,如果您正在模拟私有方法来感知要测试的东西,那么您来错地方了:永远不要测试 脏工作 它 should/may/can 改变而不改变您的测试。您需要的是测试代码行为,而不是代码的编写方式。

受保护

如果你需要测试、补丁或模拟受保护的东西,也许你的 class 隐藏了一些合作者:测试它并使用你的测试来重构你的代码,然后清理你的测试。

免责声明

的确:我在我的测试中传播了这种废话,然后当我明白我可以做得更好时,我努力将其删除。