[Tutor] Mocking with "mock" in unit testing

Steven D'Aprano steve at pearwood.info
Fri Jan 17 12:23:37 CET 2014


On Fri, Jan 17, 2014 at 09:58:06AM +0000, James Chapman wrote:

> As this question was just about mock and not really dealing with the bad
> return code or exception handling or raising my final working example looks
> like this:

Your final *working* example? I don't think so. I can see at least two 
syntax errors, and a programming error.

A word to the wise: code isn't working until you've actually seen it 
work :-)



> pinger.py
> 
> ----------------------------
> 
> import subprocess
>  class Pinger(object):

There is your first SyntaxError, a stray space ahead of the "class" 
keyword.


>     def ping_host(self, host_to_ping):
>         cmd_string = 'ping %s' % (host_to_ping)
>         cmd_args = cmd_string.split()

This is not a programming error, but it is wasteful. First you join two 
strings: "ping", and the host. Then, having glued them together, you 
split them apart exactly where you glued them.

Better to do something like: 

        cmd_args = ["ping", host_to_ping]

assuming that you know that host_to_ping is only a single word.


>         proc = subprocess.Popen(cmd_args, shell=True)

Why shell=True?

The documentation for subprocess.Popen says:

    The shell argument (which defaults to False) specifies whether to 
    use the shell as the program to execute. If shell is True, it is
    recommended to pass args as a string rather than as a sequence.

and also warns that shell=True can be a security hazard. Do you have a 
good reason for using it?

http://docs.python.org/2/library/subprocess.html



>         proc.wait()
>         if proc.returncode != 0:
>             raise Exception('Error code was: %d' % (proc.returncode))
> 
>  if __name__ == '__main__':
>     PINGER = Pinger()
>     PINGER.ping_host('localhost')
> 
> ----------------------------
> 
> 
> 
> test_pinger.py
> 
> ----------------------------
> 
> import mockimport unittestimport pinger
>  class Test_Pinger(unittest.TestCase):

And here you have two more SyntaxErrors: missing commas between 
arguments to import, and a stray space before the "class" again.


>     def test_ping_host_succeeds(self):
>         pinger = pinger.Pinger()

And here is your programming error. Unlike some programming languages 
(Lua, I think) in Python you cannot use the same name to refer to both a 
global variable and a local variable inside the same function. In 
Python, either the variable is treated as a global, or as a local, but 
not both.

The rules Python uses to decide are:

* if you declare a name "global" inside the function, then it is 
  treated as global inside that function;

* otherwise, if you assign a value to the name *anywhere* inside 
  the function, it is treated as local;

* otherwise it is global.

So in your example, you assign a value to the name "pinger", so it is 
treated as local. The offending line of code looks like this:

    pinger = pinger.Pinger()

Your *intention* is to look up the global "pinger", which is a module, 
then create a pinger.Pinger() instance, then assign it to the local 
variable "pinger". But that's not what Python does. Instead, it tries to 
look up a local variable "pinger", finds it doesn't have one, and raises 
an UnboundLocalError exception.

I recommend you change the name of the local variable "pinger" to 
something else, so it no longer clashes with the "pinger" module name.


Hope this is of help to you,



-- 
Steven


More information about the Tutor mailing list