{"id":1611,"date":"2011-04-14T17:40:13","date_gmt":"2011-04-15T01:40:13","guid":{"rendered":"http:\/\/blog.mozilla.org\/webdev\/?p=1611"},"modified":"2011-04-18T09:50:29","modified_gmt":"2011-04-18T17:50:29","slug":"a-humane-python-test-runner","status":"publish","type":"post","link":"https:\/\/blog.mozilla.org\/webdev\/2011\/04\/14\/a-humane-python-test-runner\/","title":{"rendered":"A Humane Python Test Runner"},"content":{"rendered":"<p>I have a bit of a dot dependency.<\/p>\n<p>As someone who spends a lot of time running tests on his Python code, my mind now self-administers a minute dopamine hit with every dot emitted by my test runner.<\/p>\n<pre><code>..........................................................................\r\n..................\r\n----------------------------------------------------------------------\r\nRan 93 tests in 30.182s<\/code><\/pre>\n<p>Ah, beautiful dots, each heralding a passed test. But dots have their downsides. For example, I dread early failures:<\/p>\n<pre><code>......F...................................................................\r\n...................\r\n======================================================================\r\nFAIL: test_human_path (thing.tests.test_utils.UtilsTests)\r\n----------------------------------------------------------------------\r\nTraceback (most recent call last):\r\n&nbsp;&nbsp;File \"\/Users\/erose\/thing\/tests\/test_utils.py\", line 14, in test_human_path\r\n&nbsp;&nbsp;&nbsp;&nbsp;assert False\r\nAssertionError\r\n----------------------------------------------------------------------\r\nRan 93 tests in 30.030s<\/code><\/pre>\n<p>After that F, I have to wait another 25 seconds before I get the skinny on the error. Ugh. Sure, I could ask nose to stop after the first failure (<code>--stop<\/code>), but then I wouldn&#8217;t get the chance to recognize patterns evinced by sets of similar failures.<\/p>\n<p>Another problem: once I fix the failure, how do I re-run just that test? The obvious copy-and-paste suggested by my visual cortex doesn&#8217;t work:<\/p>\n<pre><code>% nosetests thing.tests.test_utils.UtilsTests\r\n----------------------------------------------------------------------\r\nRan 0 tests in 0.000s<\/code><\/pre>\n<p>The line we actually need requires manual intervention, changing a period to a colon and reordering a few things:<\/p>\n<pre><code>% nosetests thing.tests.test_utils:UtilsTests.test_human_path<\/code><\/pre>\n<p>These and several other productivity-sappers drove me to drink (sparkling apple juice) and consider what I could do about them. After all, I had some free time while tests ran\u2014support.mozilla.com has over 1000 of them, taking almost 4 minutes in all. My answer is <a href=\"http:\/\/pypi.python.org\/pypi\/nose-progressive\/\">nose-progressive<\/a>, a more human-centric test runner that plugs into the <a href=\"http:\/\/somethingaboutorange.com\/mrl\/projects\/nose\/1.0.0\/\">nose testing framework<\/a>. Its goal? Make the best use of your perceptual apparatus and your interaction time to speed up the debugging process.<\/p>\n<h2>A Few Tempting Nuggets<\/h2>\n<p>Here are a few of nose-progressive&#8217;s features that smooth out the testing workflow.<\/p>\n<h3>Quicker, Slimmer Tracebacks<\/h3>\n<p>nose-progressive shows its tracebacks immediately; you don&#8217;t have to wait for the test run to complete. Its output looks like this:<\/p>\n<div class=\"highlight\">\n<pre><code class=\"block\"><span class=\"k\">FAIL: kitsune.apps.notifications.tests.test_events:MailTests.test_anonymous\r\n       vi +361 apps\/notifications\/tests\/test_events.py<\/span>\r\n  File \"\/opt\/local\/Library\/Frameworks\/Python.framework\/Versions\/2.6\/lib\/python2.6\/unittest.py\", line 279, in run\r\n    testMethod()\r\n  File \"\/Users\/erose\/Checkouts\/kitsune\/..\/kitsune\/apps\/notifications\/tests\/test_events.py\", line 361, in test_anonymous\r\n    eq_(1, len(mail.outbox))\r\n  File \"\/Users\/erose\/Checkouts\/kitsune\/vendor\/packages\/nose\/nose\/tools.py\", line 31, in eq_\r\n    assert a == b, msg or \"%r != %r\" % (a, b)\r\nAssertionError: 1 != 0\r\n\r\n<span class=\"k\">ERROR: kitsune.apps.questions.tests.test_templates:TemplateTestCase.test_woo\r\n        vi +494 apps\/questions\/tests\/test_templates.py<\/span>\r\n  File \"\/opt\/local\/Library\/Frameworks\/Python.framework\/Versions\/2.6\/lib\/python2.6\/unittest.py\", line 279, in run\r\n    testMethod()\r\n  File \"\/Users\/erose\/Checkouts\/kitsune\/vendor\/packages\/mock\/mock.py\", line 196, in patched\r\n    return func(*args, **keywargs)\r\n  File \"\/Users\/erose\/Checkouts\/kitsune\/..\/kitsune\/apps\/questions\/tests\/test_templates.py\", line 494, in test_woo\r\n    attrs_eq(mail.outbox[0], to=['some@bo.dy'],\r\nIndexError: list index out of range<\/code><\/pre>\n<\/div>\n<p>It strips 3 unnecessary lines off each traceback to make better use of precious terminal space: two lines worth of dividers, plus the &#8220;Traceback (most recent call last)&#8221; which is totally superfluous after you&#8217;ve been using Python for a few days. The bold does a fine job of visually delimiting tracebacks.<\/p>\n<h3>Holy Wombats, a Progress Bar<\/h3>\n<p>But where are the dots? My thoughts harkened back to the Zope test runner. It had a lovely percentage-done display that told me exactly how much apple juice I had time to drink. Why couldn&#8217;t we have that in nose but without madly scrolling my tracebacks-so-far off the screen? Drumroll, please:<\/p>\n<pre><code>thing.tests.test_templates:TaggingTests.test_add_new&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[===========\/  ]<\/code><\/pre>\n<p>That little bar on the right ticks and spins along merrily, indicating the completeness of the test run, while the path on the left names the currently running test. The whole thing lives down at the bottom of the terminal, while tracebacks scroll gaily upwards above it. If you have a lot of errors, tracebacks that push up above the top of the terminal slide helpfully into your scrollback buffer; I sidestepped curses and did the terminal addressing myself so you won&#8217;t lose anything.<\/p>\n<h3>Editor Integration<\/h3>\n<p>Did you notice those funny lines at the tops of the tracebacks?<\/p>\n<pre><code>vi +361 apps\/notifications\/tests\/test_events.py<\/code><\/pre>\n<p>Triple-click, copy, and paste that line, and <em>zoom!<\/em>\u2014you&#8217;re plunked down into your editor of choice right at your failing test. It even applies some heuristics to find the stack frame of your actual test rather than placing you unhelpfully in the middle of a nose helper function like <code>eq_()<\/code>. And you&#8217;re by no means limited to vi; nose-progressive obeys your <code>$EDITOR<\/code> environment variable. So far, it&#8217;s known to work with vi, emacs, and BBEdit.<\/p>\n<h3>Re-running Failed Tests<\/h3>\n<p>In case you want to re-run a specific failing test as above, nose-progressive gives you just what you need. Copy the pathname from this line&#8230;<\/p>\n<pre><code>FAIL: kitsune.apps.notifications.tests.test_events:MailTests.test_anonymous<\/code><\/pre>\n<p>&#8230;and paste it into your invocation of the test runner:<\/p>\n<pre><code>% nosetests kitsune.apps.notifications.tests.test_events:MailTests.test_anonymous<\/code><\/pre>\n<p>nose-progressive also gets along famously with nose&#8217;s <code>--failed<\/code> option in case you want to re-run all failing tests.<\/p>\n<h2>Getting It<\/h2>\n<p>Trying nose-progressive is a matter of two commands:<\/p>\n<pre><code>easy_install nose-progressive\r\nnosetests --with-progressive --logging-clear-handlers<\/code><\/pre>\n<p>For more details, including how to use it with Django, check out <a href=\"http:\/\/pypi.python.org\/pypi\/nose-progressive\/\">nose-progressive&#8217;s PyPI page<\/a>. Happy testing!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I have a bit of a dot dependency. As someone who spends a lot of time running tests on his Python code, my mind now self-administers a minute dopamine hit with every dot emitted by my test runner. But nose&#8217;s standard test runner has some shortcomings. I&#8217;ve written a plugin called nose-progressive that addresses them to make debugging speedier. <a class=\"go\" href=\"https:\/\/blog.mozilla.org\/webdev\/2011\/04\/14\/a-humane-python-test-runner\/\">Continue reading<\/a><\/p>\n","protected":false},"author":213,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4712,288],"tags":[],"coauthors":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v22.5 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>A Humane Python Test Runner - Mozilla Web Development<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/blog.mozilla.org\/webdev\/2011\/04\/14\/a-humane-python-test-runner\/\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Erik Rose\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"5 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/blog.mozilla.org\/webdev\/2011\/04\/14\/a-humane-python-test-runner\/\",\"url\":\"https:\/\/blog.mozilla.org\/webdev\/2011\/04\/14\/a-humane-python-test-runner\/\",\"name\":\"A Humane Python Test Runner - Mozilla Web Development\",\"isPartOf\":{\"@id\":\"https:\/\/blog.mozilla.org\/webdev\/#website\"},\"datePublished\":\"2011-04-15T01:40:13+00:00\",\"dateModified\":\"2011-04-18T17:50:29+00:00\",\"author\":{\"@id\":\"https:\/\/blog.mozilla.org\/webdev\/#\/schema\/person\/e99c85edf86c46b46e5284384d5a7c12\"},\"breadcrumb\":{\"@id\":\"https:\/\/blog.mozilla.org\/webdev\/2011\/04\/14\/a-humane-python-test-runner\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/blog.mozilla.org\/webdev\/2011\/04\/14\/a-humane-python-test-runner\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/blog.mozilla.org\/webdev\/2011\/04\/14\/a-humane-python-test-runner\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/blog.mozilla.org\/webdev\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"A Humane Python Test Runner\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/blog.mozilla.org\/webdev\/#website\",\"url\":\"https:\/\/blog.mozilla.org\/webdev\/\",\"name\":\"Mozilla Web Development\",\"description\":\"For make benefit of glorious tubes\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/blog.mozilla.org\/webdev\/?s={search_term_string}\"},\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/blog.mozilla.org\/webdev\/#\/schema\/person\/e99c85edf86c46b46e5284384d5a7c12\",\"name\":\"Erik Rose\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/blog.mozilla.org\/webdev\/#\/schema\/person\/image\/1c7953cea7e690a9e31cf08a4d68d829\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/73bfa51d6f44afed026160b59299faf2?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/73bfa51d6f44afed026160b59299faf2?s=96&d=mm&r=g\",\"caption\":\"Erik Rose\"},\"description\":\"Erik chips away at the barrier between human cognition and machine execution, through projects like DXR (search &amp; static analysis on Mozilla codebases), Fathom (semantic extraction from web pages), parsers, new languages, and a whole mess of Python libraries.\",\"sameAs\":[\"https:\/\/www.grinchcentral.com\/\",\"https:\/\/x.com\/ErikRose\"],\"url\":\"https:\/\/blog.mozilla.org\/webdev\/author\/erosemozilla-com\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"A Humane Python Test Runner - Mozilla Web Development","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/blog.mozilla.org\/webdev\/2011\/04\/14\/a-humane-python-test-runner\/","twitter_misc":{"Written by":"Erik Rose","Est. reading time":"5 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/blog.mozilla.org\/webdev\/2011\/04\/14\/a-humane-python-test-runner\/","url":"https:\/\/blog.mozilla.org\/webdev\/2011\/04\/14\/a-humane-python-test-runner\/","name":"A Humane Python Test Runner - Mozilla Web Development","isPartOf":{"@id":"https:\/\/blog.mozilla.org\/webdev\/#website"},"datePublished":"2011-04-15T01:40:13+00:00","dateModified":"2011-04-18T17:50:29+00:00","author":{"@id":"https:\/\/blog.mozilla.org\/webdev\/#\/schema\/person\/e99c85edf86c46b46e5284384d5a7c12"},"breadcrumb":{"@id":"https:\/\/blog.mozilla.org\/webdev\/2011\/04\/14\/a-humane-python-test-runner\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/blog.mozilla.org\/webdev\/2011\/04\/14\/a-humane-python-test-runner\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/blog.mozilla.org\/webdev\/2011\/04\/14\/a-humane-python-test-runner\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/blog.mozilla.org\/webdev\/"},{"@type":"ListItem","position":2,"name":"A Humane Python Test Runner"}]},{"@type":"WebSite","@id":"https:\/\/blog.mozilla.org\/webdev\/#website","url":"https:\/\/blog.mozilla.org\/webdev\/","name":"Mozilla Web Development","description":"For make benefit of glorious tubes","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/blog.mozilla.org\/webdev\/?s={search_term_string}"},"query-input":"required name=search_term_string"}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/blog.mozilla.org\/webdev\/#\/schema\/person\/e99c85edf86c46b46e5284384d5a7c12","name":"Erik Rose","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/blog.mozilla.org\/webdev\/#\/schema\/person\/image\/1c7953cea7e690a9e31cf08a4d68d829","url":"https:\/\/secure.gravatar.com\/avatar\/73bfa51d6f44afed026160b59299faf2?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/73bfa51d6f44afed026160b59299faf2?s=96&d=mm&r=g","caption":"Erik Rose"},"description":"Erik chips away at the barrier between human cognition and machine execution, through projects like DXR (search &amp; static analysis on Mozilla codebases), Fathom (semantic extraction from web pages), parsers, new languages, and a whole mess of Python libraries.","sameAs":["https:\/\/www.grinchcentral.com\/","https:\/\/x.com\/ErikRose"],"url":"https:\/\/blog.mozilla.org\/webdev\/author\/erosemozilla-com\/"}]}},"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/blog.mozilla.org\/webdev\/wp-json\/wp\/v2\/posts\/1611"}],"collection":[{"href":"https:\/\/blog.mozilla.org\/webdev\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.mozilla.org\/webdev\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.mozilla.org\/webdev\/wp-json\/wp\/v2\/users\/213"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.mozilla.org\/webdev\/wp-json\/wp\/v2\/comments?post=1611"}],"version-history":[{"count":0,"href":"https:\/\/blog.mozilla.org\/webdev\/wp-json\/wp\/v2\/posts\/1611\/revisions"}],"wp:attachment":[{"href":"https:\/\/blog.mozilla.org\/webdev\/wp-json\/wp\/v2\/media?parent=1611"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.mozilla.org\/webdev\/wp-json\/wp\/v2\/categories?post=1611"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.mozilla.org\/webdev\/wp-json\/wp\/v2\/tags?post=1611"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/blog.mozilla.org\/webdev\/wp-json\/wp\/v2\/coauthors?post=1611"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}