class 属性默认值不接受模拟

class attribute default value doesn't receive the mock

我正在对我编写的模块进行单元测试,遇到向具有模拟对象的函数提供默认 class 对象的问题。 这是它在高级中的样子:

main_file.py

class MainClass(object):
    def main_func(self):
        sub_class_obj = SubClass()
        sub_class_obj.sub_func()

sub_file.py

class SubClass(object):
    def sub_func(self, my_att=Helper(2)):
        self.my_att = my_att

helpers.py

class Helper():
    def __init__(self, my_val):
        self.my_val = my_val

test.py

class TestClass(object):
    @patch('sub_file.Helper', MockHelper)
    def my_test(self):
        main_class_obj = MainClass()
        main_class_obj.main_func()

当我以提供 my_att 的方式执行此操作时 - 一切正常并且调用了 Mock,但是当我不这样做并且设置了默认值时 - 我得到了原始的 Helper class对象。

知道如何使该属性的默认值也接收模拟吗?

提前致谢!

问题是默认值是在导入时读取的,所以在你修补 Helper 之前它已经在函数中设置好了。此时默认值保存在函数对象中。

但是,您也可以修补函数的默认参数(可以通过 __defaults__:

from sub_file import SubClass

class TestClass(object):
    @patch('sub_file.Helper', MockHelper)
    @patch.object(SubClass.sub_func, "__defaults__", (MockHelper(),))
    def my_test(self):
        main_class_obj = MainClass()
        main_class_obj.main_func()

请注意 __defaults__ 必须是参数元组。

您也可以使用 monkeypatch 来做同样的事情:

from sub_file import SubClass

class TestClass(object):
    @patch('sub_file.Helper', MockHelper)
    def my_test(self, monkeypatch):
        monkeypatch.setattr(SubClass.sub_func, "__defaults__", (MockHelper(),)     
        main_class_obj = MainClass()
        main_class_obj.main_func()

更新:
我没有意识到这不适用于 Python 2。除了默认参数的其他名称(func_defaults 而不是 __defaults__)这仅适用于独立函数,但是不使用方法,如 setattr is not supported 在这种情况下 Python 2。这是 Python 2 的解决方法:

from sub_file import SubClass

class TestClass(object):
    @patch('sub_file.Helper', MockHelper)
    def my_test(self):
        orig_sub_func = SubClass.sub_func
        with patch.object(SubClass, "sub_func",
                          lambda o, attr=MockHelper(): orig_sub_func(o, attr)):
            main_class_obj = MainClass()
            main_class_obj.main_func()

这样,原来的 sub_func 被替换为具有自己的默认值的函数,但在其他方面将功能委托给原始函数。

更新 2:
刚刚看到@chepner 的回答,这是正确的:最好的方法是相应地重构代码。只有当你做不到时,你才尝试这个答案。

默认值是在定义函数时创建的,而不是在调用函数时创建的。在你的测试中修补 Helper 已经太晚了,因为 SubClass.__init__ 已经被定义了。

不过,与其修补任何东西,不如重写 MainClass,这样就没有对 SubClass 的硬编码引用:这样您就可以自己创建适当的实例,而无需依赖默认值.

可以直接传一个实例:

class MainClass(object):
    def main_func(self, sub_class_obj):
        sub_class_obj.sub_func()

class TestClass(object):
    def my_test(self):
        mock_obj = MockHelper()
        main_class_obj = MainClass(mock_obj)
        main_class_obj.main_func()

或者采用将被调用以创建子类对象的工厂函数。

class MainClass(object):
    def main_func(self, factory=SubClass):
        sub_class_obj = factory()
        sub_class_obj.sub_func()

class TestClass(object):
    def my_test(self):
        main_class_obj = MainClass(lambda: SubClass(MockHelper()))
        main_class_obj.main_func()