[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