<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta content="text/html; charset=ISO-8859-1"
http-equiv="Content-Type">
</head>
<body bgcolor="#ffffff" text="#000000">
On 28/12/2010 15:32, Yoni Tsafir wrote:
<blockquote
cite="mid:AANLkTimZJ1aVkb5nFDPXyVAKvcRF+GF_wy=GvxXQ8oK=@mail.gmail.com"
type="cite">
<div dir="ltr">Hi guys,
<div>I'm pretty new to the world of testing in python, and
there's a problem I ran into while trying to mock a decorator
in the code I'm testing.</div>
<div>
<meta http-equiv="content-type" content="text/html;
charset=ISO-8859-1">
Tell me what am I doing wrong or if this is a missing feature
in the mock library.</div>
<div><br>
</div>
</div>
</blockquote>
<br>
The fundamental problem is that decorators are applied when the
module is imported (when the function is *created*). Attempting to
mock out the decorator later has no effect because the decorator has
already been used.<br>
<br>
You could either reorganise your code to have catch_bad_args
delegate to another function that you can mock or patch both
sys.stderr and sys.exit and test the code by its effects.<br>
<br>
I would be tempted to have catch_bad_args call a _report_bad_args
function with the exception instance. You could then just patch this
function to test the behaviour.<br>
<br>
Something like:<br>
<br>
<br>
def catch_bad_args(func):<br>
def call(*args, **kwargs):<br>
try:<br>
return func(*args, **kwargs)<br>
except BadArgsError, e:<br>
_report_bad_args(e)<br>
<br>
def _report_bad_args(e):<br>
print >> sys.stderr, e.args[0]<br>
print<br>
sys.exit(1)<br>
<br>
In general having code call sys.exit directly makes it harder to
test. If possible you should organise your application to have a
single code path for exiting, with a single point that needs mocking
/ patching to override this behaviour.<br>
<br>
If your application entry point looks (something) like this:<br>
<br>
def main():<br>
try:<br>
args = handle(args)<br>
except BadArgsError as e:<br>
_handle_bad_args(e)<br>
return 1<br>
else:<br>
do_other_stuff()<br>
<br>
if __name__ == '__main__':<br>
result = main()<br>
sys.exit(result)<br>
<br>
Then you can test your main function handles bad arguments without
worrying about it calling sys.exit().<br>
<br>
To test the return code with bad arguments you can use subprocess or
similar as a complete "functional test" that executes the
application in the same way as the user would.<br>
<br>
All the best,<br>
<br>
Michael<br>
<blockquote
cite="mid:AANLkTimZJ1aVkb5nFDPXyVAKvcRF+GF_wy=GvxXQ8oK=@mail.gmail.com"
type="cite">
<div dir="ltr">
<div>I have the following code:</div>
<div><br>
</div>
<div>a.py:</div>
<div>-------</div>
<div>
<div>import sys</div>
<div><br>
</div>
<div>class BadArgsError(Exception):</div>
<div> pass</div>
<div><br>
</div>
<div>
<br>
</div>
<div>def catch_bad_args(func):</div>
<div> def call(*args, **kwargs):</div>
<div> try:</div>
<div> return func(*args, **kwargs)</div>
<div> except BadArgsError, e:</div>
<div> print >> sys.stderr, e.args[0]</div>
<div> print</div>
<div> sys.exit(1)</div>
<div><br>
</div>
<div> call.__name__ = func.__name__</div>
<div> call.__dict__ = func.__dict__</div>
<div> call.__doc__ = func.__doc__</div>
<div> return call</div>
<div style="text-decoration: underline;"><br>
</div>
</div>
<div>b.py</div>
<div>------</div>
<div>
<div>from a import BadArgsError, catch_bad_args</div>
<div><br>
</div>
<div>@catch_bad_args</div>
<div>def foo(arg):</div>
<div> if arg > 2:</div>
<div> raise BadArgsError("arg is bigger than 2")</div>
<div><br>
</div>
<div> print "arg is ok"</div>
</div>
<div><br>
</div>
<div>test_b.py</div>
<div>-------------</div>
<div>
<div>import unittest</div>
<div>from b import foo</div>
<div>from a import BadArgsError</div>
<div>from mock import patch</div>
<div> </div>
<div>def do_nothing_decorator(func):</div>
<div> return func</div>
<div> </div>
<div> </div>
<div>
class BTest(unittest.TestCase):</div>
<div> @patch("a.catch_bad_args", do_nothing_decorator)</div>
<div> def test_three_raises(self):</div>
<div> self.assertRaises(BadArgsError, foo, 3)</div>
<div>
<div><br>
</div>
<div><br>
</div>
<div>
<meta http-equiv="content-type" content="text/html;
charset=ISO-8859-1">
<div>Now running the tests:</div>
</div>
<div>-------------------------------</div>
<div>
<div># python test_b.py</div>
<div>arg is bigger than 2</div>
<div>
<br>
</div>
<div>E</div>
<div>======================================================================</div>
<div>ERROR: test_three_raises (__main__.BTest)</div>
<div>----------------------------------------------------------------------</div>
<div>Traceback (most recent call last):</div>
<div> File "test_b.py", line 13, in test_three_raises</div>
<div> self.assertRaises(BadArgsError, foo, 3)</div>
<div> File "/usr/lib/python2.6/unittest.py", line 336, in
failUnlessRaises</div>
<div> callableObj(*args, **kwargs)</div>
<div> File "/tmp/a.py", line 14, in call</div>
<div> sys.exit(1)</div>
<div>SystemExit: 1</div>
<div><br>
</div>
<div>----------------------------------------------------------------------</div>
<div>Ran 1 test in 0.014s</div>
<div><br>
</div>
<div>FAILED (errors=1)</div>
</div>
</div>
</div>
<div><br>
</div>
<div><br>
</div>
<div>What am I doing wrong? I tried to change the patch to
"b.catch_bad_args" and I get the same results, I also tried
@patch.object and other combinations... No help</div>
<div><br>
</div>
<div><br>
</div>
<div>Thanks a lot!</div>
</div>
<pre wrap="">
<fieldset class="mimeAttachmentHeader"></fieldset>
_______________________________________________
testing-in-python mailing list
<a class="moz-txt-link-abbreviated" href="mailto:testing-in-python@lists.idyll.org">testing-in-python@lists.idyll.org</a>
<a class="moz-txt-link-freetext" href="http://lists.idyll.org/listinfo/testing-in-python">http://lists.idyll.org/listinfo/testing-in-python</a>
</pre>
</blockquote>
<br>
<br>
<pre class="moz-signature" cols="72">--
<a class="moz-txt-link-freetext" href="http://www.voidspace.org.uk/">http://www.voidspace.org.uk/</a>
May you do good and not evil
May you find forgiveness for yourself and forgive others
May you share freely, never taking more than you give.
-- the sqlite blessing <a class="moz-txt-link-freetext" href="http://www.sqlite.org/different.html">http://www.sqlite.org/different.html</a>
</pre>
</body>
</html>