{"id":1125,"date":"2010-01-11T22:45:32","date_gmt":"2010-01-11T12:45:32","guid":{"rendered":"http:\/\/www.somethinkodd.com\/oddthinking\/?p=1125"},"modified":"2010-01-11T22:45:32","modified_gmt":"2010-01-11T12:45:32","slug":"pythons-doctest-considered-harmful","status":"publish","type":"post","link":"https:\/\/www.somethinkodd.com\/oddthinking\/2010\/01\/11\/pythons-doctest-considered-harmful\/","title":{"rendered":"Python&#8217;s doctest considered harmful."},"content":{"rendered":"<p>This is a message from the Julian of 2010, to any Julian of the future who is starting out a new software project.<\/p>\n<p>Message begins: Doctest is a interesting and clever testing harness built right in to Python. Try not to use it. Message ends.<\/p>\n<hr \/>\n<p>On second thoughts, maybe it is a mistake to assume that messages to the future have limited bandwidth. I probably have room to explain further.<\/p>\n<p>As you know, Python allows you to annotate each module, class and method with a &#8220;docstring&#8221; &#8211; a special multi-line comment which describes it, and is available to various meta-tools.<\/p>\n<p>One way to describe a function is to provide examples on how to call it and what you might expect when it returns.<\/p>\n<p>Here&#8217;s a snippet from the example in the doctest documentation:<\/p>\n<blockquote>\n<pre><code>\r\ndef factorial(n):\r\n    \"\"\"Return the factorial of n, an exact integer >= 0.\r\n\r\n    If the result is small enough to fit in an int, return an int.\r\n    Else return a long.\r\n\r\n    >>> [factorial(n) for n in range(6)]\r\n    [1, 1, 2, 6, 24, 120]\r\n    >>> [factorial(long(n)) for n in range(6)]\r\n    [1, 1, 2, 6, 24, 120]\r\n\t\r\n    [snip]\"\"\"<\/code><\/pre>\n<\/blockquote>\n<p>Doctest is built around an interesting idea. <\/p>\n<p>Doctest offers a way of testing these examples. Call <code>doctest.testmod()<\/code> and it will examine the docstrings in the current module, looking for specially formatted examples (note the <code>>>><\/code> prefix), execute the code and check it against the provided example results. <\/p>\n<div class=\"aside\">Python has an idiom for test cases.<\/p>\n<p>If you put code like this at the bottom of your module:<\/p>\n<pre><code>if __name__ == '__main__':\r\n    import doctest\r\n    doctest.testmod()<\/code><\/pre>\n<p>it will be executed if you run the module as a program, but if you import it as a library for another module to use, it won&#8217;t be executed. It&#8217;s a good place to invoke unit-tests, so you can run them immediately when you edit the code, but they stay out of the way when the unit actually gets used in anger.<\/p><\/div>\n<p>So doctest is a great way to check your code&#8217;s comments don&#8217;t get out of date. Well, one very limited aspect of your code&#8217;s comments. If that&#8217;s all you are using it for, that&#8217;s fine.<\/p>\n<p>But, Julian, it&#8217;s not how you use it, is it?  For simple functions with just two test cases, and two examples, why would you bother creating a whole unit-test, when you have everything you need already there?<\/p>\n<p>Another test case? Why not just throw it in as another example, rather creating a separate unit-test. Two more? How confusing could it be?<\/p>\n<p>Oh what&#8217;s that? You now have ten examples of how to call your code and it turns out it can be very confusing? That&#8217;s okay, because doctest has a great facility to extend testing into a separate file. Put all your examples in a file called &#8220;examples.doctest&#8221; and call <code>doctest.testfile(\"examples.doctest\")<\/code>.<\/p>\n<p>Oh, don&#8217;t forget you&#8217;ll need to add an &#8220;import&#8221; statement, to load your original module&#8230; and probably more for all the types that appear in function parameters. But still, how about that! A really quick unit-test framework, for practically no effort!<\/p>\n<p>Except that every time you try to execute the current document in your IDE, it will get shirty. Windows doesn&#8217;t know how to run a doctest, and nor does Python. You need to find the code file being tested, and run that instead. <\/p>\n<p>After you have made that mistake 50 times on any a particular file, you&#8217;ll be tempted to hack around it. Put <code>\"\"\"<\/code> marks around the whole doctest file to make it a docstring, append the magic idiom for unit-tests, above. Then some magic cookies to the top for your Unix code, or teach your editor to run it as a Python file in Windows, or even rename it with a .py extension. Now you have a fully executable test code.<\/p>\n<p>Oh, except every reference to the newline character, <code>\\n<\/code> (or other special characters) needs to be double-slashed as <code>\\\\n<\/code>, but only when it is executed as testmod (when it is treated as a text file), and not as testfile (when it is treated as a docstring) so good luck working around that.<\/p>\n<p>More importantly, look at what you&#8217;ve done here.<\/p>\n<p>You&#8217;ve started using a new language. It is similar to Python, but not quite. Every line needs to be prefixed with <code>>>><\/code> or <code>...<\/code> to show it should be executed, the escaping rules are different, and, most importantly, all your fancy language-based tools can&#8217;t figure it out.<\/p>\n<p>No more syntax-colouring.<\/p>\n<p>No more more autocompletion.<\/p>\n<p>No more pylint suggestions.<\/p>\n<p>All that, just to avoid using a real unit-testing framework?<\/p>\n<p>Not worth it. Just not worth it.<\/p>\n<p>So, dear Julian of the future (sorry, may I call you Julian, or should I be addressing you as Mr President?), please don&#8217;t do that again, okay? <\/p>\n<p>Oh, and if you are going to reply to this message, please remember to include this week&#8217;s lotto numbers. Thanks.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Doctest is a interesting and clever testing harness built right in to Python. Try not to use it.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_s2mail":"","footnotes":""},"categories":[25,34],"tags":[],"class_list":["post-1125","post","type-post","status-publish","format-standard","hentry","category-insufficiently-advanced-technology","category-software-development"],"_links":{"self":[{"href":"https:\/\/www.somethinkodd.com\/oddthinking\/wp-json\/wp\/v2\/posts\/1125","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.somethinkodd.com\/oddthinking\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.somethinkodd.com\/oddthinking\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.somethinkodd.com\/oddthinking\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.somethinkodd.com\/oddthinking\/wp-json\/wp\/v2\/comments?post=1125"}],"version-history":[{"count":8,"href":"https:\/\/www.somethinkodd.com\/oddthinking\/wp-json\/wp\/v2\/posts\/1125\/revisions"}],"predecessor-version":[{"id":1133,"href":"https:\/\/www.somethinkodd.com\/oddthinking\/wp-json\/wp\/v2\/posts\/1125\/revisions\/1133"}],"wp:attachment":[{"href":"https:\/\/www.somethinkodd.com\/oddthinking\/wp-json\/wp\/v2\/media?parent=1125"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.somethinkodd.com\/oddthinking\/wp-json\/wp\/v2\/categories?post=1125"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.somethinkodd.com\/oddthinking\/wp-json\/wp\/v2\/tags?post=1125"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}