Re: [Tutor] Mocking with mock in unit testing
Thanks eryksun. There's a list for testing in python which I also posed the question to and got pretty much the same answer as you provided. The line if proc.returncode != 1 was a mistake. That 1 should have been a zero. 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: pinger.py import subprocess class Pinger(object): def ping_host(self, host_to_ping): cmd_string = 'ping %s' % (host_to_ping) cmd_args = cmd_string.split() proc = subprocess.Popen(cmd_args, shell=True) 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): def test_ping_host_succeeds(self): pinger = pinger.Pinger() with mock.patch(pinger.subprocess) as subprocess: subprocess.Popen.return_value.returncode = 0 pinger.ping_host('localhost') subprocess.Popen.assert_called_once_with(['ping','localhost'], shell=True) def test_ping_host_fails_and_throws_exception(self): pinger = pinger.Pinger() with mock.patch('pinger.subprocess') as subprocess: subprocess.Popen.return_value.returncode = 1 self.assertRaises(Exception, pinger.ping_host, 'localhost') if __name__ == '__main__': unittest.main() -- James On 17 January 2014 01:05, eryksun eryk...@gmail.com wrote: On Thu, Jan 16, 2014 at 5:32 AM, James Chapman ja...@uplinkzero.com wrote: 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? You can mock `Popen` where it's accessed. @mock.patch('subprocess.Popen') def test_ping_host(self, Popen): Popen.return_value.returncode = 0 pinger = Pinger() pinger.ping_host('127.0.0.1') Popen.assert_called_once_with(['ping','127.0.0.1'], shell=True) If the tutor_q module imported `Popen` into its namespace, then you'd patch tutor_q.Popen. def ping_host(self, host_to_ping): cmd_string = 'ping %s' % (host_to_ping) cmd_args = cmd_string.split() Splitting on spaces doesn't work generally. Use `shlex.split`, or build the list manually. proc = subprocess.Popen(cmd_args, shell=True) Maybe you really need the shell to process your command, but generally there's no reason to run the shell just to have it execute the command and wait. Plus it opens the door to security exploits. proc.wait() if proc.returncode != 1: raise Exception('Error code was: %d' % (proc.returncode)) A non-zero return code signals an error. When using `Popen` directly, you can be consistent with `check_call` and `check_output` if you raise a `CalledProcessError` in this case: retcode = proc.wait() if retcode: raise subprocess.CalledProcessError(retcode, cmd_args) ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: https://mail.python.org/mailman/listinfo/tutor
Re: [Tutor] Mocking with mock in unit testing
On Fri, Jan 17, 2014 at 4:58 AM, James Chapman ja...@uplinkzero.com wrote: import mock import unittest import pinger class Test_Pinger(unittest.TestCase): def test_ping_host_succeeds(self): pinger = pinger.Pinger() Are you using CPython? That raises an UnboundLocalError. Take a look at the CPython bytecode: def test(self): pinger = pinger.Pinger() dis.dis(test) 2 0 LOAD_FAST1 (pinger) 3 LOAD_ATTR0 (Pinger) 6 CALL_FUNCTION0 9 STORE_FAST 1 (pinger) 12 LOAD_CONST 0 (None) 15 RETURN_VALUE Notice LOAD_FAST(1), where fast local 1 is the local variable `pinger`. The value isn't assigned yet, and LOAD_FAST doesn't search globals and builtins. You need a unique name for the instance, such as `test_pinger`. Then the compiler knows to use LOAD_GLOBAL: def test(self): test_pinger = pinger.Pinger() dis.dis(test) 2 0 LOAD_GLOBAL 0 (pinger) 3 LOAD_ATTR1 (Pinger) 6 CALL_FUNCTION0 9 STORE_FAST 1 (test_pinger) 12 LOAD_CONST 0 (None) 15 RETURN_VALUE Here's a version using a decorator instead: @mock.patch('pinger.subprocess') def test_ping_host_succeeds(self, subprocess): subprocess.Popen.return_value.returncode = 0 test_pinger = pinger.Pinger() test_pinger.ping_host('localhost') subprocess.Popen.assert_called_once_with(['ping','localhost'], shell=True) ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: https://mail.python.org/mailman/listinfo/tutor
Re: [Tutor] Mocking with mock in unit testing
On Fri, Jan 17, 2014 at 09:58:06AM +, 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 ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: https://mail.python.org/mailman/listinfo/tutor
Re: [Tutor] Mocking with mock in unit testing
Erm...? CPython yeah. If I rename pinger.py to tutor.py and change the unittest it runs fine. Why? - import mock import unittest import tutor class Test_Pinger(unittest.TestCase): def test_ping_host_succeeds(self): pinger = tutor.Pinger() with mock.patch(tutor.subprocess) as subprocess: subprocess.Popen.return_value.returncode = 0 pinger.ping_host('localhost') subprocess.Popen.assert_called_once_with(['ping','localhost'], shell=True) def test_ping_host_fails_and_throws_exception(self): pinger = tutor.Pinger() with mock.patch('tutor.subprocess') as subprocess: subprocess.Popen.return_value.returncode = 1 self.assertRaises(Exception, pinger.ping_host, 'localhost') if __name__ == '__main__': unittest.main() - -- James On 17 January 2014 10:50, eryksun eryk...@gmail.com wrote: On Fri, Jan 17, 2014 at 4:58 AM, James Chapman ja...@uplinkzero.com wrote: import mock import unittest import pinger class Test_Pinger(unittest.TestCase): def test_ping_host_succeeds(self): pinger = pinger.Pinger() Are you using CPython? That raises an UnboundLocalError. Take a look at the CPython bytecode: def test(self): pinger = pinger.Pinger() dis.dis(test) 2 0 LOAD_FAST1 (pinger) 3 LOAD_ATTR0 (Pinger) 6 CALL_FUNCTION0 9 STORE_FAST 1 (pinger) 12 LOAD_CONST 0 (None) 15 RETURN_VALUE Notice LOAD_FAST(1), where fast local 1 is the local variable `pinger`. The value isn't assigned yet, and LOAD_FAST doesn't search globals and builtins. You need a unique name for the instance, such as `test_pinger`. Then the compiler knows to use LOAD_GLOBAL: def test(self): test_pinger = pinger.Pinger() dis.dis(test) 2 0 LOAD_GLOBAL 0 (pinger) 3 LOAD_ATTR1 (Pinger) 6 CALL_FUNCTION0 9 STORE_FAST 1 (test_pinger) 12 LOAD_CONST 0 (None) 15 RETURN_VALUE Here's a version using a decorator instead: @mock.patch('pinger.subprocess') def test_ping_host_succeeds(self, subprocess): subprocess.Popen.return_value.returncode = 0 test_pinger = pinger.Pinger() test_pinger.ping_host('localhost') subprocess.Popen.assert_called_once_with(['ping','localhost'], shell=True) ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: https://mail.python.org/mailman/listinfo/tutor
Re: [Tutor] Mocking with mock in unit testing
Ah of course! pinger = pinger.Pinger() I should have noticed that myself. (I renamed the file to pinger.py after establishing it all worked and then didn't re-run). ping = pinger.Pinger() works fine. As for the syntax errors, those will be copy paste / different email client errors. -- James On 17 January 2014 11:23, Steven D'Aprano st...@pearwood.info wrote: On Fri, Jan 17, 2014 at 09:58:06AM +, 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 ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: https://mail.python.org/mailman/listinfo/tutor ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: https://mail.python.org/mailman/listinfo/tutor
Re: [Tutor] Mocking with mock in unit testing
On Fri, Jan 17, 2014 at 6:23 AM, Steven D'Aprano st...@pearwood.info wrote: On Fri, Jan 17, 2014 at 09:58:06AM +, James Chapman wrote: import subprocess class Pinger(object): There is your first SyntaxError, a stray space ahead of the class keyword. The rich text version is correct (and colorful), but a non-breaking space ended up on a line in between the span elements in the pre block: span s= tyle=3Dcolor:rgb(220,20,60)subprocess/span =C2=A0 span style=3Dcolor:rgb(255,119,0);font-weight:boldclass/span The automated conversion to plain text removed the line break. Better to do something like: cmd_args = [ping, host_to_ping] assuming that you know that host_to_ping is only a single word. If it isn't a single word, then use `shlex.split`, as I previously suggested. Why shell=True? and also warns that shell=True can be a security hazard. Do you have a good reason for using it? I already asked this and got the response this question was just about mock. 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. This is also fine in the rich text version. BTW, the botched plain text conversion is missing line breaks, not commas. 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. As you say, for a function. Optimized code requires the name to be bound locally, else it raises UnboundLocalError. The unoptimized code of a class body, on the other hand, can load a name from globals or builtins and then store the same name to locals. x = 'global x' class C(object): ... x = x + , but now local ... print x ... global x, but now local x 'global x' ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: https://mail.python.org/mailman/listinfo/tutor
Re: [Tutor] Mocking with mock in unit testing
Really! import mock import unittest import pinger It should be three lines, but somehow it got all messed up, either through rich text formatting or copy paste. Being a bit pedantic now about import statements which are clearly unintentionally messed up. - Sent in plain text. -- James On 17 January 2014 15:32, Steven D'Aprano st...@pearwood.info wrote: On Fri, Jan 17, 2014 at 08:35:04AM -0500, eryksun wrote: On Fri, Jan 17, 2014 at 6:23 AM, Steven D'Aprano st...@pearwood.info wrote: On Fri, Jan 17, 2014 at 09:58:06AM +, James Chapman wrote: [...] 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. This is also fine in the rich text version. BTW, the botched plain text conversion is missing line breaks, not commas. I don't understand this. If I'm interpreting you correctly, import mockimport unittestimport pinger would not be fine. -- Steven ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: https://mail.python.org/mailman/listinfo/tutor ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: https://mail.python.org/mailman/listinfo/tutor
Re: [Tutor] Mocking with mock in unit testing
Steven D'Aprano st...@pearwood.info Wrote in message: On Fri, Jan 17, 2014 at 08:35:04AM -0500, eryksun wrote: On Fri, Jan 17, 2014 at 6:23 AM, Steven D'Aprano st...@pearwood.info wrote: On Fri, Jan 17, 2014 at 09:58:06AM +, James Chapman wrote: [...] 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. This is also fine in the rich text version. BTW, the botched plain text conversion is missing line breaks, not commas. I don't understand this. If I'm interpreting you correctly, import mockimport unittestimport pinger would not be fine. You're putting the linefeeds in the wrong places. import mock import unittest import pinger -- Steven ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: https://mail.python.org/mailman/listinfo/tutor -- DaveA nr ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: https://mail.python.org/mailman/listinfo/tutor
Re: [Tutor] Mocking with mock in unit testing
On Fri, Jan 17, 2014 at 08:35:04AM -0500, eryksun wrote: On Fri, Jan 17, 2014 at 6:23 AM, Steven D'Aprano st...@pearwood.info wrote: On Fri, Jan 17, 2014 at 09:58:06AM +, James Chapman wrote: [...] 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. This is also fine in the rich text version. BTW, the botched plain text conversion is missing line breaks, not commas. I don't understand this. If I'm interpreting you correctly, import mockimport unittestimport pinger would not be fine. -- Steven ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: https://mail.python.org/mailman/listinfo/tutor
Re: [Tutor] Mocking with mock in unit testing
On Fri, Jan 17, 2014 at 10:32 AM, Steven D'Aprano st...@pearwood.info wrote: import mockimport unittestimport pinger would not be fine. Screenshot: http://i.imgur.com/wSihI1X.png The following is in a pre tag, so the whitespace is rendered literally: span style=S0import/span mock span style=S0import/span span style=S1unittest/span span style=S0import/span pinger span style=S0class/span --- Replace S0 and S1 as follows: S0 = color:rgb(255,119,0);font-weight:bold S1 = color:rgb(220,20,60) There's a non-breaking space on the otherwise empty line between pinger and class. ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: https://mail.python.org/mailman/listinfo/tutor
Re: [Tutor] Mocking with mock in unit testing
On Fri, Jan 17, 2014 at 11:05:24AM -0500, Dave Angel wrote: Steven D'Aprano st...@pearwood.info Wrote in message: import mockimport unittestimport pinger would not be fine. You're putting the linefeeds in the wrong places. Of course I am! You would be amazed at how long I had to stare at this before I even noticed the import suffix at the end of the first two lines. I had actually convinced myself that somehow two of the three import words had been deleted, not just whitespace. It's all clear to me now, the raw HTML code in James' email had: span style=color:rgb(255,119,0);font-weight:boldimport/span mock span style=color:rgb(255,119,0);font-weight:boldimport/span span style=color:rgb(220,20,60)unittest/span span style=color:rgb(255,119,0);font-weight:boldimport/span pinger (indentation added by me) which got mangled into plain text as: import mockIMPORT unittestIMPORT pinger (highlighting the latter two imports for emphasis), and not what my brain was telling me, which was import mock unittest pinger. -- Steven ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: https://mail.python.org/mailman/listinfo/tutor
Re: [Tutor] Mocking with mock in unit testing
On Fri, Jan 17, 2014 at 03:43:58PM +, James Chapman wrote: Really! import mock import unittest import pinger It should be three lines, but somehow it got all messed up, either through rich text formatting or copy paste. Yes, I see what's going on now, excuse the noise. But this is a good example of why HTML-based rich text is rubbish. Being a bit pedantic now about import statements which are clearly unintentionally messed up. You're asking for help on a mailing list aimed at beginners. It is very common to have beginners post code which cannot possible run and claim that it is the version of the code they ran. How are we supposed to tell which screw ups are unintentional and which are intentional? -- Steven ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: https://mail.python.org/mailman/listinfo/tutor
Re: [Tutor] Mocking with mock in unit testing
On 18/01/14 00:49, Steven D'Aprano wrote: which got mangled into plain text as: import mockIMPORT unittestIMPORT pinger (highlighting the latter two imports for emphasis), and not what my brain was telling me, which was import mock unittest pinger. If its any consolation I did exactly the same. Even after the correction came out and I went back to the original I still missed the import. It was only when the raw HTML came through I saw it... -- Alan G Author of the Learn to Program web site http://www.alan-g.me.uk/ http://www.flickr.com/photos/alangauldphotos ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: https://mail.python.org/mailman/listinfo/tutor
Re: [Tutor] Mocking with mock in unit testing
On Thu, Jan 16, 2014 at 5:32 AM, James Chapman ja...@uplinkzero.com wrote: 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? You can mock `Popen` where it's accessed. @mock.patch('subprocess.Popen') def test_ping_host(self, Popen): Popen.return_value.returncode = 0 pinger = Pinger() pinger.ping_host('127.0.0.1') Popen.assert_called_once_with(['ping','127.0.0.1'], shell=True) If the tutor_q module imported `Popen` into its namespace, then you'd patch tutor_q.Popen. def ping_host(self, host_to_ping): cmd_string = 'ping %s' % (host_to_ping) cmd_args = cmd_string.split() Splitting on spaces doesn't work generally. Use `shlex.split`, or build the list manually. proc = subprocess.Popen(cmd_args, shell=True) Maybe you really need the shell to process your command, but generally there's no reason to run the shell just to have it execute the command and wait. Plus it opens the door to security exploits. proc.wait() if proc.returncode != 1: raise Exception('Error code was: %d' % (proc.returncode)) A non-zero return code signals an error. When using `Popen` directly, you can be consistent with `check_call` and `check_output` if you raise a `CalledProcessError` in this case: retcode = proc.wait() if retcode: raise subprocess.CalledProcessError(retcode, cmd_args) ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: https://mail.python.org/mailman/listinfo/tutor