从__init__中删除工作以帮助进行单元测试

Lav*_*ven 7 python object-initializers

这个问题的关键是帮助单元测试.如果我有一个繁忙的__init__(即__init__复杂的初始化),我不能简单地实例化一个类的对象,但我需要模拟/存根调用在依赖关系中的所有方法__init__.

为了说明这个问题,这里是一个例子:

class SomeClass(object):
    def __init__(self, dep1, dep2, some_string):
        self._dep1 = dep1
        self._dep2 = dep2
        self._some_string = some_string

        # I would need to mock everything here (imagine some even more
        # complicated example)
        for dep2element in self._dep2:
            dep2element.set_dep(dep1)
        self._dep1.set_some_string(some_string)

    def fun1(self):
        ...
    def fun2(self):
        ...
    def fun3(self):
        ...
Run Code Online (Sandbox Code Playgroud)

要测试这些fun*功能,每个测试都必须执行复杂的构造.

class TestSomeClass(TestCase):
    def create_SomeClass(self, some_string):
        dep1 = Mock()
        # mock everything required by SomeClass' constructor

        dep2 = Mock()
        # mock everything required by SomeClass' constructor

        return SomeClass(dep1, dep2, some_string)

    def test_fun1(self):
        sc = self.create_SomeClass('some string')
        ...

    def test_fun2(self):
        sc = self.create_SomeClass('some other string')
        ...

    def test_fun3(self):
        sc = self.create_SomeClass('yet another string')
        ...
Run Code Online (Sandbox Code Playgroud)

我发现这是多余的,并想知道如何在python中优雅地处理这些问题,如果不是通过从构造函数移动工作.

解:

正如@ecatmur建议的那样,要测试一些特定的函数,这段代码应该可以解决问题:

def test_some_method():
    mobject = Mock(SomeClass)
    SomeClass.fun1(mobject)
Run Code Online (Sandbox Code Playgroud)

通过这种方法,所有方法都将被模拟出来.如果fun1调用你想要执行的其他方法(例如fun2),你可以这样做:

def test_some_method():
    mobject = Mock(SomeClass)
    mobject.fun2 = SomeClass.fun2.__get__(mobject)
    SomeClass.fun1(mobject)
Run Code Online (Sandbox Code Playgroud)

SomeClass.fun2.__get__(mobject)将产生instancemethod哪些将提供正确的绑定.

¡Viva el Python!

原始问题:

最初的问题集中在将完成的工作__init__转移到单独的init方法和不同的问题旋转该方法.我通常的做法是做到这一点

class SomeClass(object):
    def __init__(self, dep1, dep2, some_string)
        self._dep1 = dep1
        self._dep2 = dep2

        # lots of mumbo-jumbo here...
Run Code Online (Sandbox Code Playgroud)

成为这个

class SomeClass(object):
    def __init__(self, dep1, dep2)
        self._dep1 = dep1
        self._dep2 = dep2

    def initiate(self, some-string)
        # lots of mumto-jumbo here...
Run Code Online (Sandbox Code Playgroud)

一般的情绪是,移动工作__init__不是一种常见的做法,对经验丰富的python开发人员来说毫无意义.

eca*_*mur 8

如果您编写初始化函数,__init__那么经验丰富的开发人员肯定会将您的代码视为孩子的游乐场.

如果您担心能够在不运行__init__方法的情况下创建看起来像您的类实例的对象,那么使用Mock:

def test_some_method():
    mock_object = Mock(MyClass)
    MyClass.some_method(mock_object)
Run Code Online (Sandbox Code Playgroud)


Hug*_*ell 5

__init__实际上无法返回任何东西;如果您考虑如何使用它,这应该很明显:

class Example(object):
    def __init__(self, x):
        self.x = x
        return ANYTHING

e = Example(1)   # how are you going to get ANYTHING back?
Run Code Online (Sandbox Code Playgroud)

使用与initialize()分开的方法__init__似乎很愚蠢-整个要点是,初始化器应在创建对象时自动运行,因此示例初始化应类似于

scobj = SomeClass(dep1, dep2, 'abcdef')
# scobj.initialize('abcdef')     # <= separate call not needed!
Run Code Online (Sandbox Code Playgroud)

编辑:

如果您确实需要在中加入代码__init__,建议将其放在私有方法中,然后从中调用__init__,例如:

class Example2(object):
    def __init__(self, a, b, c):
        self._init_connection(a)
        self._init_display(b)
        self.c = c

    def _init_connection(self, a):
        self.conn = make_connection(a)

    def _init_display(self, b):
        self.disp = make_display(b)
Run Code Online (Sandbox Code Playgroud)

...这很好,因为

  • 所有初始化都在创建对象时完成(您无需记住进行后续初始化调用)
  • 您不必保留标志来记住对象是否已初始化-如果存在,则对象已初始化。