可以在不模拟对象的其他属性的情况下模拟 python 构造函数吗?
Can a python constructor be mocked without mocking other properties of the object?
是否可以模拟 python 构造函数,同时继续使用同名其他 fields/functions 的生产版本?例如,给定生产代码:
class MyClass:
class SubClass:
def __init__(self) -> None:
print("\nreal sub init called")
class SubSubClass:
def __init__(self) -> None:
print("\nreal sub sub init called")
和以下测试代码:
class FakeSubClass:
def __init__(self) -> None:
print("\nfake init called")
def test():
MyClass.SubClass()
MyClass.SubClass.SubSubClass()
MyClass.SubClass = Mock(side_effect=FakeSubClass)
MyClass.SubClass()
MyClass.SubClass.SubSubClass()
我们得到以下输出:
real sub init called
real sub sub init called
fake init called
请注意,最后一行 MyClass.SubClass.SubSubClass()
没有创建真正的 SubSubClass,因为此时它是一个自动创建的 属性 的 SubClass mock。
我想要的输出如下:
real sub init called
real sub sub init called
fake init called
real sub sub init called
换句话说,我只想模拟 SubClass,而不是 SubSubClass。我已经尝试代替上面的模拟行的东西(两者都不起作用):
MyClass.SubClass.__init__ = Mock(side_effect=FakeSubClass.__init__)
MyClass.SubClass.__new__ = Mock(side_effect=FakeSubClass.__new__)
请注意,我知道有几种重构代码的方法可以避免这个问题,但遗憾的是代码无法重构。
你也可以伪造 class,MyClass.SubClass.SubSubClass()
在你的情况下不起作用是因为 MyClass.SubClass
是一个没有 SubSubClass
定义的 Mock。只需让 FakeSubClass
从 MyClass.SubClass 继承它就可以解决问题。
并且您可以轻松地将 MyClass
修补为 FakeClass
,并且您将拥有正确的测试对象而不是真实对象。
from unittest.mock import Mock
class MyClass:
class SubClass:
def __init__(self) -> None:
print("\nreal sub init called")
class SubSubClass:
def __init__(self) -> None:
print("\nreal sub sub init called")
class FakeSubClass(MyClass.SubClass, Mock):
def __init__(self) -> None:
print("\nfake init called")
class FakeClass:
class SubClass(FakeSubClass):
pass
def test():
MyClass.SubClass()
MyClass.SubClass.SubSubClass()
MyClass.SubClass = Mock(side_effect=FakeSubClass)
FakeClass.SubClass()
FakeClass.SubClass.SubSubClass()
我同意 ZhouQuan 的回答非常好,因为它适用于 MyClass.Subclass
上的任何方法或变量。也就是说,这里有一些可能有用也可能没用的变体。
如果 FakeSubClass
由于某种原因无法直接编辑,或者您只想继承 SubSubClass()
而没有别的,可以像这样在 test()
中进行更改。
def test():
MyClass.SubClass()
MyClass.SubClass.SubSubClass()
FakeSubClass.SubSubClass = MyClass.SubClass.SubSubClass()
MyClass.SubClass = Mock(side_effect=FakeSubClass)
MyClass.SubClass()
MyClass.SubClass.SubSubClass()
我认为可能值得注意的是 Mock
确实接受了一个 wraps
参数,它可以用来获得 similar 行为,尽管它是不完全是你要求的。这是一个例子。
from unittest.mock import Mock
class MyClass:
class SubClass:
def __init__(self) -> None:
print("\nreal sub init called")
class SubSubClass:
def __init__(self) -> None:
print("\nreal sub sub init called")
class FakeSubClass:
def __init__(self) -> None:
print("\nfake init called")
def test():
MyClass.SubClass()
MyClass.SubClass.SubSubClass()
MyClass.SubClass = Mock(side_effect=FakeSubClass, wraps=MyClass.SubClass)
MyClass.SubClass()
MyClass.SubClass.SubSubClass()
这给出了不同的输出。
real sub init called
real sub sub init called
fake init called # A call to MyClass.SubClass() causes both real and fake inits.
real sub init called # Same MyClass.SubClass() call.
real sub sub init called # But now the SubSubClass() does resolve correctly.
是否可以模拟 python 构造函数,同时继续使用同名其他 fields/functions 的生产版本?例如,给定生产代码:
class MyClass:
class SubClass:
def __init__(self) -> None:
print("\nreal sub init called")
class SubSubClass:
def __init__(self) -> None:
print("\nreal sub sub init called")
和以下测试代码:
class FakeSubClass:
def __init__(self) -> None:
print("\nfake init called")
def test():
MyClass.SubClass()
MyClass.SubClass.SubSubClass()
MyClass.SubClass = Mock(side_effect=FakeSubClass)
MyClass.SubClass()
MyClass.SubClass.SubSubClass()
我们得到以下输出:
real sub init called
real sub sub init called
fake init called
请注意,最后一行 MyClass.SubClass.SubSubClass()
没有创建真正的 SubSubClass,因为此时它是一个自动创建的 属性 的 SubClass mock。
我想要的输出如下:
real sub init called
real sub sub init called
fake init called
real sub sub init called
换句话说,我只想模拟 SubClass,而不是 SubSubClass。我已经尝试代替上面的模拟行的东西(两者都不起作用):
MyClass.SubClass.__init__ = Mock(side_effect=FakeSubClass.__init__)
MyClass.SubClass.__new__ = Mock(side_effect=FakeSubClass.__new__)
请注意,我知道有几种重构代码的方法可以避免这个问题,但遗憾的是代码无法重构。
你也可以伪造 class,MyClass.SubClass.SubSubClass()
在你的情况下不起作用是因为 MyClass.SubClass
是一个没有 SubSubClass
定义的 Mock。只需让 FakeSubClass
从 MyClass.SubClass 继承它就可以解决问题。
并且您可以轻松地将 MyClass
修补为 FakeClass
,并且您将拥有正确的测试对象而不是真实对象。
from unittest.mock import Mock
class MyClass:
class SubClass:
def __init__(self) -> None:
print("\nreal sub init called")
class SubSubClass:
def __init__(self) -> None:
print("\nreal sub sub init called")
class FakeSubClass(MyClass.SubClass, Mock):
def __init__(self) -> None:
print("\nfake init called")
class FakeClass:
class SubClass(FakeSubClass):
pass
def test():
MyClass.SubClass()
MyClass.SubClass.SubSubClass()
MyClass.SubClass = Mock(side_effect=FakeSubClass)
FakeClass.SubClass()
FakeClass.SubClass.SubSubClass()
我同意 ZhouQuan 的回答非常好,因为它适用于 MyClass.Subclass
上的任何方法或变量。也就是说,这里有一些可能有用也可能没用的变体。
如果 FakeSubClass
由于某种原因无法直接编辑,或者您只想继承 SubSubClass()
而没有别的,可以像这样在 test()
中进行更改。
def test():
MyClass.SubClass()
MyClass.SubClass.SubSubClass()
FakeSubClass.SubSubClass = MyClass.SubClass.SubSubClass()
MyClass.SubClass = Mock(side_effect=FakeSubClass)
MyClass.SubClass()
MyClass.SubClass.SubSubClass()
我认为可能值得注意的是 Mock
确实接受了一个 wraps
参数,它可以用来获得 similar 行为,尽管它是不完全是你要求的。这是一个例子。
from unittest.mock import Mock
class MyClass:
class SubClass:
def __init__(self) -> None:
print("\nreal sub init called")
class SubSubClass:
def __init__(self) -> None:
print("\nreal sub sub init called")
class FakeSubClass:
def __init__(self) -> None:
print("\nfake init called")
def test():
MyClass.SubClass()
MyClass.SubClass.SubSubClass()
MyClass.SubClass = Mock(side_effect=FakeSubClass, wraps=MyClass.SubClass)
MyClass.SubClass()
MyClass.SubClass.SubSubClass()
这给出了不同的输出。
real sub init called
real sub sub init called
fake init called # A call to MyClass.SubClass() causes both real and fake inits.
real sub init called # Same MyClass.SubClass() call.
real sub sub init called # But now the SubSubClass() does resolve correctly.