<div dir="ltr">Thank you Patrick.<div><br></div><div>This does help as it gives me a working start point to go from. Prior to your message I was being caught out by the line:</div><div><br></div><div><span style="font-family:'courier new',monospace;font-size:13px">subprocess.Popen.return_value.</span><span style="font-family:'courier new',monospace;font-size:13px">returncode = 0</span><br>
</div><div><span style="font-family:'courier new',monospace;font-size:13px"><br></span></div><div><span style="font-size:13px"><font face="arial, helvetica, sans-serif">so cheers for that.</font></span></div><div>
<span style="font-size:13px"><font face="arial, helvetica, sans-serif"><br></font></span></div><div><br></div><div class="gmail_extra"><div>--<br>James<br></div>
<div class="gmail_extra"><br></div><div class="gmail_extra"><br></div><br><br><div class="gmail_quote">On 16 January 2014 13:32, Patrick Smith <span dir="ltr"><<a href="mailto:pjs482@gmail.com" target="_blank">pjs482@gmail.com</a>></span> wrote:<br>
<blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div dir="ltr"><div class="im">> You're quite right, I've not mocked the subprocess class which thinking about it is really what I want to do and somehow inject that into the test so that when I call Pinger it uses the mock subprocess class rather than the real one.<div>
<br></div></div><div>That's exactly what you need to do and what `mock.patch` will accomplish for you. Consider the following the test code:</div><div><br></div><div><div><div class="im"><div><font face="courier new, monospace"> def test_ping_host(self):</font></div>
<div><font face="courier new, monospace"> pinger = tutor_q.Pinger()</font></div></div><div class="im"><div><font face="courier new, monospace"> with mock.patch("tutor_q.subprocess") as subprocess:</font></div>
</div><div><font face="courier new, monospace"> subprocess.Popen.return_value.returncode = 0</font></div>
<div><font face="courier new, monospace"> pinger.ping_host('localhost')</font></div><div><font face="courier new, monospace"> subprocess.Popen.assert_called_once_with(['ping','localhost'], shell=True)</font></div>
</div><div><font face="courier new, monospace"><br></font></div><div>The important line here is `with mock.patch("tutor_q.subprocess") as subprocess:`. `mock.patch` will create a mock object and replace `subprocess` in the `tutor_q` module with that mock object (and then re-insert the real `subprocess` when the block is exited). That mock object is then assigned to a variable named `subprocess` that you can use in your tests (note: it can be assigned to any name you want, but using the same name as what's being mocked out will make the tests more readable).</div>
<div><br></div><div>The line where you're setting the return value of the mock also needs to be changed. If you do `subprocess.Popen.return_value = 0`, then in the `ping_host` method, `proc` will be 0, since it's the result of calling `subprocess.Popen`. So, you won't be able to do things like `proc.wait()` or `proc.return_code` since those attributes don't exist on an integer. Instead, you can set the `return_code` on `subprocess.Popen.return_value`, which is itself a Mock object.</div>
<div class="gmail_extra"><br>Hope this helps!</div><span class="HOEnZb"><font color="#888888"><div class="gmail_extra"><br></div><div class="gmail_extra">- Patrick</div></font></span><div><div class="h5"><div class="gmail_extra">
<br><div class="gmail_quote">On Thu, Jan 16, 2014 at 6:22 AM, James Chapman <span dir="ltr"><<a href="mailto:james@uplinkzero.com" target="_blank">james@uplinkzero.com</a>></span> wrote:<br>
<blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-color:rgb(204,204,204);border-left-style:solid;padding-left:1ex"><div dir="ltr">Thanks Ned and Tiemo (not sure your mail went to the list?). You're quite right, I've not mocked the subprocess class which thinking about it is really what I want to do and somehow inject that into the test so that when I call Pinger it uses the mock subprocess class rather than the real one. Although as of writing I'm still unsure how to do that?<div>
<br></div><div>The code I've used in the question is a simplified version of what I'm actually doing. ping was substituted for a tool that will not be available on a continuous integration build system. If I can understand and get a simplified mock to work, then I'll hopefully be able to use my new found knowledge to mock out the real test.</div>
<div class="gmail_extra"><br clear="all"><div>--<br>James<br></div><div><div>
<br><br><div class="gmail_quote">On 16 January 2014 11:04, Ned Batchelder <span dir="ltr"><<a href="mailto:ned@nedbatchelder.com" target="_blank">ned@nedbatchelder.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-color:rgb(204,204,204);border-left-style:solid;padding-left:1ex">
<div text="#000000" bgcolor="#FFFFFF"><div><div>
On 1/16/14 5:40 AM, James Chapman wrote:<br>
<blockquote type="cite">
<div dir="ltr">
<div class="gmail_quote">Originally sent to python tutor mailing
list but I suspect this might be a better list for my
question...<br>
<br>
<div dir="ltr">
<div>Hi all<br>
</div>
<div dir="ltr">
<div><br>
</div>
<div>
I have a question regarding mocking in unit testing.</div>
<div><br>
</div>
<div>Let's assume I have the following class:</div>
<div><br>
</div>
<div><font face="courier new, monospace">-------------------------------------------</font></div>
<div><font face="courier new, monospace">import subprocess<br>
</font></div>
<div><font face="courier new, monospace"><br>
</font></div>
<div>
<div><font face="courier new, monospace">class
Pinger(object):</font></div>
<div><font face="courier new, monospace"><br>
</font></div>
<div><font face="courier new, monospace"> def
ping_host(self, host_to_ping):</font></div>
<div><font face="courier new, monospace">
cmd_string = 'ping %s' % (host_to_ping)</font></div>
<div><font face="courier new, monospace">
cmd_args = cmd_string.split()</font></div>
<div><font face="courier new, monospace"> proc =
subprocess.Popen(cmd_args, shell=True)</font></div>
<div><font face="courier new, monospace">
proc.wait()</font></div>
<div><font face="courier new, monospace"> if
proc.returncode != 1:</font></div>
<div><font face="courier new, monospace">
raise Exception('Error code was: %d' %
(proc.returncode))</font></div>
</div>
<div>
<div><font face="courier new, monospace"><br>
</font></div>
<div><font face="courier new, monospace">-------------------------------------------</font></div>
<div><br>
</div>
<div><br>
</div>
<div>In my unittest I don't want to run the ping
command, (It might not be available on the build
system) I merely want to check that a call to
subprocess.Popen is made and that the parameters are
what I expect?</div>
<div><br>
</div>
<div>So far I have this, but it doesn't work and I
suspect it's way off!!</div>
<div><br>
</div>
<div><br>
</div>
<div><font face="courier new, monospace">-------------------------------------------<br>
</font></div>
<div>
<div><font face="courier new, monospace">import mock</font></div>
<div><font face="courier new, monospace">import
unittest</font></div>
<div><font face="courier new, monospace">from tutor_q
import Pinger</font></div>
<div>
<font face="courier new, monospace"><br>
</font></div>
<div><font face="courier new, monospace">class
Test_Pinger(unittest.TestCase):</font></div>
<div><font face="courier new, monospace"><br>
</font></div>
<div><font face="courier new, monospace"> def
test_ping_host(self):</font></div>
<div><font face="courier new, monospace">
pinger = Pinger()</font></div>
<div><font face="courier new, monospace">
assert pinger</font></div>
<div><font face="courier new, monospace">
subprocess = mock.Mock()</font></div>
<div><font face="courier new, monospace">
subprocess.Popen.return_value = 0</font></div>
<div><font face="courier new, monospace">
subprocess.assert_called_once_with(['ping','localhost'])</font></div>
<div><font face="courier new, monospace">
pinger.ping_host('127.0.0.1')</font></div>
<div><font face="courier new, monospace"><br>
</font></div>
</div>
</div>
</div>
</div>
</div>
</div>
</blockquote></div></div>
Often the trickiest thing about mocking is mocking the right name.
Here you've created a mock, and it's assigned to a local variable
named subprocess. This name is not related to the global subprocess
name in the tutor_q module, which is the name that will be used by
your pinger object.<br>
<br>
You have to assign to that global. A module's globals are also
available as attributes on the module object, so you can do it like
this:<br>
<br>
<font face="courier new, monospace"><br>
</font>
<div>
<div><font face="courier new, monospace">import mock</font></div>
<div><font face="courier new, monospace">import unittest</font></div>
<div><font face="courier new, monospace">import tutor_q<br>
</font></div>
<div>
<font face="courier new, monospace"><br>
</font></div>
<div><font face="courier new, monospace">class
Test_Pinger(unittest.TestCase):</font></div>
<div><font face="courier new, monospace"><br>
</font></div>
<div><font face="courier new, monospace"> def
test_ping_host(self):</font></div>
<div><font face="courier new, monospace"> pinger =
tutor_q.Pinger()</font></div><div>
<div><font face="courier new, monospace"> assert pinger</font></div>
<div><font face="courier new, monospace"> subprocess =
mock.Mock()</font></div>
<div><font face="courier new, monospace">
subprocess.Popen.return_value = 0</font></div>
</div><div><font face="courier new, monospace">
tutor_q.subprocess = subprocess</font><font face="courier new,
monospace"><br>
pinger.ping_host('127.0.0.1')</font></div>
</div><div>
<font face="courier new, monospace"><font face="courier new,
monospace"> </font>subprocess.assert_called_once_with(['ping','localhost'])</font>
<br>
<br></div>
I've also moved the assert to after the code under test, since
that's when the calls will be available to make assertions about. (I
didn't actually test this code, if I got it wrong, someone on the
list will school me!)<br>
<br>
A problem with this code is that you replace tutor_q.subprocess with
a mock, but you don't restore it when the test is done. Mock has
tools that can do this for you:<br>
<br>
<div><font face="courier new, monospace"><br>
</font></div>
<div><font face="courier new, monospace"> def
test_ping_host(self):</font></div>
<div><font face="courier new, monospace"> pinger =
tutor_q.Pinger()</font></div>
<div><font face="courier new, monospace"> assert pinger</font></div>
<div><font face="courier new, monospace"> with
mock.patch("tutor_q.subprocess") as subprocess:<br>
subprocess = mock.Mock()</font></div>
<div><font face="courier new, monospace">
subprocess.Popen.return_value = 0</font></div>
<div><font face="courier new, monospace"> </font><font face="courier new, monospace">pinger.ping_host('127.0.0.1')</font></div><div>
<font face="courier new, monospace"><font face="courier new,
monospace"> </font>subprocess.assert_called_once_with(['ping','localhost'])</font>
<br>
<br></div>
Note that there is an unfortunate coupling of your test code and
product code: the test had to know how subprocess was imported. In
tutor_q.py, if you had instead used "from subprocess import Popen",
then your mock would have to be similarly adjusted to mock
tutor_q.Popen instead of tutor_q.subprocess.<br>
<br>
--Ned.<br>
<blockquote type="cite"><div>
<div dir="ltr">
<div class="gmail_quote">
<div dir="ltr">
<div dir="ltr">
<div>
<div>
<div>
<font face="courier new, monospace"><br>
</font></div>
<div><font face="courier new, monospace"><br>
</font></div>
<div><font face="courier new, monospace">if __name__
== '__main__':</font></div>
<div><font face="courier new, monospace">
unittest.main()</font></div>
</div>
<div><font face="courier new, monospace"><br>
</font></div>
<div><font face="courier new, monospace">-------------------------------------------</font><br>
</div>
<div><br>
</div>
<div><br>
</div>
<div>Can anyone point me in the right direction on how
to mock up these subprocess calls?</div>
<div><br>
</div>
<div>Thanks</div>
<span><font color="#888888">
<div><br>
</div>
<div>James</div>
<div><br>
</div>
<div><br>
</div>
<div><br>
</div>
</font></span></div>
</div>
</div>
</div>
<br>
</div>
<br>
<fieldset></fieldset>
<br>
</div><pre>_______________________________________________
testing-in-python mailing list
<a href="mailto:testing-in-python@lists.idyll.org" target="_blank">testing-in-python@lists.idyll.org</a>
<a href="http://lists.idyll.org/listinfo/testing-in-python" target="_blank">http://lists.idyll.org/listinfo/testing-in-python</a>
</pre>
</blockquote>
<br>
</div>
<br>_______________________________________________<br>
testing-in-python mailing list<br>
<a href="mailto:testing-in-python@lists.idyll.org" target="_blank">testing-in-python@lists.idyll.org</a><br>
<a href="http://lists.idyll.org/listinfo/testing-in-python" target="_blank">http://lists.idyll.org/listinfo/testing-in-python</a><br>
<br></blockquote></div><br></div></div></div></div>
<br>_______________________________________________<br>
testing-in-python mailing list<br>
<a href="mailto:testing-in-python@lists.idyll.org" target="_blank">testing-in-python@lists.idyll.org</a><br>
<a href="http://lists.idyll.org/listinfo/testing-in-python" target="_blank">http://lists.idyll.org/listinfo/testing-in-python</a><br>
<br></blockquote></div><br></div></div></div></div></div>
</blockquote></div><br></div></div>