[TIP] Dilemma with using mock objects

John Wong gokoproject at gmail.com
Tue Apr 24 09:24:24 PDT 2012


Dear Tom and Herman,

Thank you guys for the genuine responses. They are helpful.

First, Herman:

>In a script like the one you describe, I would try to group all the
> side-effecty IO related code into a single place and isolate it as
> much as possible from the logic of the rest of the program. That way,
> if you provide a single entry point or a wrapper for the IO code, you
> will be able to easily test the flow of the program and not have to
> worry about complicated mocking of built-in functions.

I am guessing you want me to create a small function that handles the
logics inside the for loop (or iteritems())?


>       for src, dest in src_dest:
>         if os.path.exists(src):
>           if os.path.exists(dest):
>              shutil.rmtree(dest)
>           shutil.move(src, dest)   # either way we will proceed to move
>         else:
>           return (False, '%s does not exist!' % src)
>       return (True, 'Success!')

By skipping, yes, I want the "else" clause of `if os.path.exists(dest)`
(meaning, it doesn't exist yet). By mocking "os.path.exists" to be True, it
probably will make it True throughout. In that one special test, how can I
separate the patch? How can I stop the patch at a particular point?

> Finally, instead of using the exception system you are
> bulk-catching exceptions and returning error indicators instead. As you
> mentioned above, this gives you the annoying requirement of figuring out
every
> possible exception instead of leaving it to the caller to properly handle
them.

I don't want to raise any exception, that's why I returns a tuple (False,
e). As a web app, rasie exception is bad. I figure it's a good idea to just
return False, along with the exception indicator. It's up to the user (in
this case, at the moment, me) to log that exception, and return nice error
message.

I think you are referring to `side_effect` which I think I need to
explicitly supply the actual exception, right? Is there a more general way
to do the side effect (I wouldn't know all the exceptions that the method
is capable of generating)?

I think I can do this

import unittest
from shutil import Error
import shutil
from mock import patch

class TestSequenceFunctions(unittest.TestCase):
    @patch.object(shutil, 'move')
    def test2(self, mocker):
        import shutil
        mocker.side_effect = Error
        # mocker.side_effect = Exception  <--- this is fine too
        src = '/home/ted/tester/1'
        dest = '/home/ted/tester/2'
        #result = shutil.move(src, dest)
        #mocker.assert_called_with(src, dest)
        self.assertRaises(Error, mocker, src, dest)    # we can assert
Exception instead of Error as well

So what's the downside of always using `Exception` over explicit exception
class such as shutill.Error? My implementation catches `Exception` so I
feel like I've generalised it (Error will be caught as well).


Last, but not the least, mock objects supposed to mimic all the real thing,
but it doesn't actually catch exception unless we supply a side-effect? I
tried to minix `shutil.move(src, dest)` with dest being a directory that
already exists. The real object will throw exception because dest must not
already exists.


Thanks.

Ted




On Mon, Apr 23, 2012 at 10:47 PM, Tom Davis <tom at recursivedream.com> wrote:

> On Mon, 23 Apr 2012 18:24:49 -0400, Tu Ted wrote:
> > Please bear with me. Suppose I want to assert that result is in a
> > particular format, I might set the return value to something as (True,
> > 'Success')
> >
> > Sample code: http://pastebin.com/VraxaKjL
> >
> > def batch_move(self, *args, **kwargs):
> >   '''
> >   Move a batch of files to its respective destinations.
> >   Return type: tuple (boolean, message)
> >                       T/F    , 'string' / str(exception)
> >   '''
> >
> >   srcs= kwargs.get('srcs', None)
> >   dests = kwargs.get('dests', None)
> >
> >   try:
> >     if srcs and dests:
> >        # map srcs and dests into dictionary (srcs --> keys, dests -->
> > values)
> >        src_dest= dict(zip(srcs, dests))
> >
> >        for src, dest in src_dest:
> >          if os.path.exists(src):
> >            if os.path.exists(dest):
> >               shutil.rmtree(dest)
> >            shutil.move(src, dest)   # either way we will proceed to move
> >          else:
> >            return (False, '%s does not exist!' % src)
> >        return (True, 'Success!')
> >
> >     else:
> >        return (False, 'Something gone wrong with those kwargs...')
> >   except Exception as e:
> >     return (False, e)
>
> I think you probably need src_dest.iteritems(), for starters...
>
> >
> > In order to get to return (True, 'Success!'), I have
> >
> > 1. patch os.path.exists with True as return value. But in one unittest I
> > want to skip this, how do I patch that os.path.exists?
> >         if os.path.exists(dest):  # I want to skip this
> >              shutil.rmtree(dest)
> >
>
> If by "skip" you mean go to the "else" block, you could just configure the
> mock
> to return False instead. If by "skip" you mean you want the actual
> function to
> be called, just don't mock that test (if using a decorator) or stop the
> patch
> (if patching manually).
>
> >
> >
> > 2. How do I patch shutil.move(src, dest)? Do I just give True so it
> doesn't
> > generate error? What if I want the case it fails and caught an exception?
> > How do I simulate that? (I wouldn't always know which exception to catch,
> > the primarily reason to use Exception as e).
>
> You should test every case, both success and failure. You can consult the
> documentation or the source for `shutil.move` in order to write tests for
> each
> failure case. In the case that neither of these are completely helpful,
> just try
> different scenarios and see what happens. Why would moves fail? Destination
> exists, insufficient permissions, etc.
>
> >
> > 3. If I actually pass the function, does it really mean no exception
> caught
> > and it went through every single line? Or is it because I set
> > `mock_object.return_value = (True, 'Success!')?
>
> A library like coverage.py will tell you that more easily, but if all you
> did
> was mock exists() and move(), then you could get every other line to run
> (presuming you had True/False cases that ultimately cover every code path).
>
> Now, there are a number of ways you're making your life more difficult by
> the
> way this function is written. First, it has to validate its arguments
> manually;
> that adds another code path (bad kwargs). Second, it couples the success
> of the
> function with the presentation of its failure; this could lead you to test
> oft-altered string values (which probably have no place in this function to
> begin with). Finally, instead of using the exception system you are
> bulk-catching exceptions and returning error indicators instead. As you
> mentioned above, this gives you the annoying requirement of figuring out
> every
> possible exception instead of leaving it to the caller to properly handle
> them.
>
> >
> > 4. I am only using two dependencies here, do I need to patch out all the
> > external dependencies such as (os, sys, math, datetime) all in one? Or if
> > my function is using other functions (which are refactored)
> >
> >
> >  def f1(*args, **kwargs):
> >     f2(..)   # use math, plot, datetime
> >     f3(..)   # use math and datetime
> >     f4(..)   # use datetime
>
> You only need to patch that which you do not want to run as normal. In the
> example you gave, you should technically mock calls to each of f2(), f3(),
> and
> f4(), simply asserting on the values passed to them. This, of course,
> presumes
> you have written tests for each of these functions elsewhere, thus do not
> need
> to test them indirectly a second time.
>
> >
> >
> > Thanks. Sorry for the long questions. I really want to be good at writing
> > unittests.
>
> An excellent goal to those on this list, I am sure ;)
>
> Hopefully my answers were somewhat useful...
>
> >
> >
> > --
> > Ted
>
> > _______________________________________________
> > testing-in-python mailing list
> > testing-in-python at lists.idyll.org
> > http://lists.idyll.org/listinfo/testing-in-python
>
>
> _______________________________________________
> testing-in-python mailing list
> testing-in-python at lists.idyll.org
> http://lists.idyll.org/listinfo/testing-in-python
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.idyll.org/pipermail/testing-in-python/attachments/20120424/ccefb94d/attachment.htm>


More information about the testing-in-python mailing list