Removing work from __init__ to support unit testing

The key to this is helping with unit testing. If I have a busy __init__ (i.e. __init__ that does complex initialization), I can't just instantiate the class object, but I need to mock all the methods called in the dependencies inside __init__ . p>

To illustrate this problem, here is an example:

 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): ... 

To test the fun* functions, each test must perform a complex construct.

 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') ... 

I find this redundant and would like to know how these problems can be elegantly handled in python, if not by moving work from the constructor.

DECISION:

As @ecatmur suggested, in order to test a specific function, this code should do the trick:

 def test_some_method(): mobject = Mock(SomeClass) SomeClass.fun1(mobject) 

With this approach, all methods will be mocked. If fun1 calls another method that you want to execute (e.g. fun2 ), you can do it like this:

 def test_some_method(): mobject = Mock(SomeClass) mobject.fun2 = SomeClass.fun2.__get__(mobject) SomeClass.fun1(mobject) 

SomeClass.fun2.__get__(mobject) will create an instancemethod that will ensure the correct binding.

ยกViva el Python!

ORIGINAL QUESTION:

The original question centered around moving the work done in __init__ to a separate init method and various issues that rotate this approach. My usual approach is to do this

 class SomeClass(object): def __init__(self, dep1, dep2, some_string) self._dep1 = dep1 self._dep2 = dep2 # lots of mumbo-jumbo here... 

do it

 class SomeClass(object): def __init__(self, dep1, dep2) self._dep1 = dep1 self._dep2 = dep2 def initiate(self, some-string) # lots of mumto-jumbo here... 

The general consensus was that moving work from __init__ not common practice and would be pointless for experienced python developers.

+6
source share
5 answers

If you write initialization functions separately from __init__ , then experienced developers will definitely see your code as a playground.

If you are concerned that you can create objects similar to instances of your class without using the __init__ method, use Mock :

 def test_some_method(): mock_object = Mock(MyClass) MyClass.some_method(mock_object) 
+7
source

__init__ cannot return anything; if you think about how it is used, this should be obvious:

 class Example(object): def __init__(self, x): self.x = x return ANYTHING e = Example(1) # how are you going to get ANYTHING back? 

Using the initialize() method, separate from __init__ , seems silly - the thing is that your initializer should automatically start when you create the object, so the initialization of your example should look like

 scobj = SomeClass(dep1, dep2, 'abcdef') # scobj.initialize('abcdef') # <= separate call not needed! 

Edit:

If you really need to specify the code in __init__ , I suggest putting it in private methods and calling those from __init__ , for example:

 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) 

... that's good because

  • all initialization is done when the object is created (you do not need to remember about subsequent initialization calls)
  • You do not need to save the flag in order to remember whether your object was initialized or not - if it exists, it was initialized.
+4
source
 class Klass: def __init__(self): initialize instance here, no return 

__init__ will automatically start when an object is created, so you do not need to check whether it starts; it was running if you have an instance!

About returning myself from init (): I would not, since this is an operation in place. Read the first answer here , as this explains it pretty well.

+2
source

What about

 class SomeClass(object): def __init__(self, dep1, dep2, st=None): self._dep1 = dep1 self._dep2 = dep2 if st is None: self._initialized = False else: self.initialize(st) def initialize(self, st): ... self._initialized = True ... 

?

+1
source

There is nothing wrong with breaking __init__() into separate methods. If you can make it so that these methods are properly named and reused, then everything is better - for example:

 class SomeClass(object): def __init__(self, dep1, dep2, some_string): self.set_dependencies(dep1, dep2, some_string) def set_dependencies(self, dep1, dep2, some_string): self._dep1 = dep1 self._dep2 = dep2 self._some_string = some_string for dep2element in self._dep2: dep2element.set_dep(dep1) self._dep1.set_some_string(some_string) 

Here, the remove_dependencies() method may be added in the future to return the instance to a neutral state before installing new dependencies. Functionality is disconnected from the initialization process.

Note. I called it from __init__() , although we do not want this to be done during testing. Unless we call it from __init__() , someone who uses this class would have to call set_dependencies() after creating the instance - which is an unnecessary complication for your class API.

What we can do is drown out the method for testing, for example:

 class TestSomeClass(TestCase): def __init__(self): SomeClass._old_set_dependencies = SomeClass.set_dependencies SomeClass.set_dependencies = lambda *args: None ... 

This is a pretty impudent way to cross out a method, just to prove that there really are good libraries to do it more diligently - see this discussion , but I also recommend Mockstar , which is an extension of Mock , to make semantics easier.

+1
source

Source: https://habr.com/ru/post/925844/


All Articles