> How is having 15 arguments in a .create() method better than having 15 
> arguments in __init__() ?
> So, if you use the create() method, and it sets up internal data structures, 
> how do you test them?  In other words, if create() makes that queue then how 
> do you test with a half-empty queue?
> Not all design patterns make sense in every language.
 
Seems that there is still some unclarity about the whole proposal, so I combine 
your questions into an example. But first, little more background.

Generally, with testing, it would be optimal to test outputs of the system for 
given inputs without caring how things are implemented. That way, any changes 
in implementation won't affect test results. Very trivial example would be 
something like this:

def do_something_important(input1, input2, input3)
    return  # something done with input1, input2, input3

Implementation of do_something_important can be one liner, or it can contain 
multiple classes, yet the result of the function is what matters. That's also 
the basic strategy (one of many) I try to follow with when testing the code I 
write.

Now, testing the class using queue (as an example) should follow same pattern, 
thus a simple example how to test it would look like this (assuming that 
everyone knows how to use mock library):

# This is just an example testing with DI and mocks.

class Example:
   def __init__(self, queue):
       self._queue = queue

   def can_add(self):
       return not self._queue.full()  

def TestExample(unittest.TestCase):
    def setUp(self):
        self.queue = mock.MagicMock()

    def 
test_it_should_be_possible_to_know_when_there_is_still_room_for_items(self):
       self.queue.full.return_value = False
       example = Example(self.queue)
       self.assertTrue(example.can_add())

    def 
test_it_should_be_possible_to_know_when_no_more_items_can_be_added(self):
       self.queue.full.return_value = True
       example = Example(self.queue)
       self.assertFalse(example.can_add())

In above example, example doesn't really care what class the object is. Only 
full method is needed to be implemented. Injected class can be Queue, 
VeryFastQueue or LazyQueue, as long as they implement method "full" 
(duck-typing!). Test takes advantage of that and changing the implementation 
won't break the tests (tests are not caring how Example is storing the Queue). 
 Also, adding more cases is trivial and should also make think the actual 
implementation and what is needed to be taken care of. For example, 
self.queue.full.return_value = None, or self.queue.full.side_effect = 
ValueError(). How should code react on those?

Then comes the next step, doing the actual DI. One solution is:

class Example:
    def __init__(self, queue=None):
        self._queue = queue or Queue()

Fine approach, but technically __init__ has two execution branches and someone 
staring blindly coverages might require covering those too. Then we can use 
class method too.

class Example:
    def __init__(self, queue):
        self._queue = queue

    @classmethod
    def create(cls):
        q = Queue()
        # populate_with_defaults
        # Maybe get something from db too for queue...
        return cls(q)

As said, create-method is for convenience. it can (and should) contain minimum 
set of arguments needed from user (no need to be 15 even if __init__ would 
require it) to create the object. It creates the fully functioning Example 
object with default dependencies. Do notice that tests I wrote earlier would 
still work. Create can contain slow executing code, if needed, but it won't 
slow down testing the Example class itself. 

Finally, if you want to be tricky and write own decorator for object 
construction, Python would allow you to do that.

@spec('queue')  # generates __init__ that populates instance with queue given 
as arg
class Example:
    @classmethod
    def create(cls):
        return cls(Queue())

Example can still be initialized calling Example(some_dependency), or calling 
Example.create() which provides default configuration. Writing the decorator 
would give unlimited ways to extend the class. And test written in the 
beginning of the post would still pass.
-- 
https://mail.python.org/mailman/listinfo/python-list

Reply via email to