<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Obey the Testing Goat!</title><link href="http://www.obeythetestinggoat.com/" rel="alternate"></link><link href="http://www.obeythetestinggoat.com/feeds/all.atom.xml" rel="self"></link><id>http://www.obeythetestinggoat.com/</id><updated>2024-08-07T16:13:00+01:00</updated><subtitle></subtitle><entry><title>Progress on the Third Edition of the Book!</title><link href="http://www.obeythetestinggoat.com/progress-on-the-third-edition-of-the-book.html" rel="alternate"></link><published>2024-08-07T16:13:00+01:00</published><updated>2024-08-07T16:13:00+01:00</updated><author><name>Harry</name></author><id>tag:www.obeythetestinggoat.com,2024-08-07:progress-on-the-third-edition-of-the-book.html</id><summary type="html">&lt;p&gt;In lieu of a formal announcement about the Third Edition, how about a progress update?&lt;/p&gt;
&lt;h2&gt;Core technology updates: Django + Python&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Embarrassment-Driven Development&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One of the main motivations for a third edition was that the 2e is based on Django 1.11,
which dropped out of support back in 2017, and that's been a big turnoff for readers for a while,
and quite embarrassing really.&lt;/p&gt;
&lt;p&gt;So, the plan is to upgrade to Django 5.x, and progress is good -- I've already updated
most of the core chapters to Django 4.2, and upgrade Python to 3.12 while I was at it.
Django 5 is next, and I'm hoping/assuming it will be a smaller leap that 1-&amp;gt;4 was,
so that won't be far behind.&lt;/p&gt;
&lt;h2&gt;New Deployment Technologies:  Docker + Ansible&lt;/h2&gt;
&lt;p&gt;I've always been proud that the book includes several chapters on how to actually deploy our app
to production, and make the app live on the actual public Internet.
But the deployment process from the first and second editions--broadly speaking,
SSH in to your server, hack about to figure out how to get your app deployed manually,
and then automate what you did with glorified shellscripts, aka Fabric--was starting
to look less and less like what modern deployment looks like, or my experience of it at least.&lt;/p&gt;
&lt;p&gt;I uhmmed and ahhed about it for a while, but in the end I decided to go with a deployment
process that looks like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Package up our app into a Docker container, and use our tests to confirm it really works&lt;/li&gt;
&lt;li&gt;Use Ansible to automate pushing that container onto a server and running it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Check out the latest version of the deployment chapters here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/book/chapter_09_docker.html"&gt;Chapter 9: Containerization aka Docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/book/chapter_10_production_readiness.html"&gt;Chapter 10: Making our App Production-Ready&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/book/chapter_11_ansible.html"&gt;Chapter 11: Infrastructure As Code: Automated Deployments With Ansbile&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I think I like how it's turned out,
a lot of the fiddliness and debugging of deployment/production-readiness can now happen locally
(in Docker containers on your own machine),
so I think that tightens and speeds up the feedback loop a fair bit.&lt;/p&gt;
&lt;h2&gt;JavaScript&lt;/h2&gt;
&lt;p&gt;The Javascript chapter was another head-scratcher.
I wanted to move away from QUnit, and include some more modern/ES6 syntax.
In the end, I decided to go with Jasmine, which is old but still popular,
but to keep the browser-based test runner, which is a bit of an unconventional choice,
but it does mean we can avoid the whole Node.js and &lt;code&gt;node_modules&lt;/code&gt; learning curve.&lt;/p&gt;
&lt;p&gt;Aside from that, I've wound down the "JavaScript is such a nightmare" jokes,
because they're really not fair any more, and were probably never that funny besides.&lt;/p&gt;
&lt;p&gt;Check out the new version here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/book/chapter_16_javascript.html"&gt;Chapter 16: A Gentle Excursion Into JavaScript&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Some changes of emphasis&lt;/h2&gt;
&lt;p&gt;The other main changes to the book are going to be around how
I talk about some of the tradeoffs involved in the use of mocking,
and unit vs integration vs functional/e2e tests.
I think the first and second editions were perhaps a little too opinionated on this front
(I still cringe to think how defensive I was when I first wrote the &lt;a href="/book/chapter_hot_lava.html"&gt;Hot Lava&lt;/a&gt; chapter, sorry CaseY!!),
and my thinking has evolved a lot since I wrote my &lt;a href="https://www.cosmicpython.com/"&gt;second book&lt;/a&gt; with Bob.&lt;/p&gt;
&lt;p&gt;That's still very much on the drawing board though,
so you'll have to watch this space for updates on that front.&lt;/p&gt;
&lt;p&gt;Anyways, all the latest versions of the 3e chapters are live here on the site,
and also as an &lt;a href="https://learning.oreilly.com/library/view/test-driven-development-with/9781098148706/"&gt;Early Release on O'Reilly Learning&lt;/a&gt;,
so do dive in and let me know what you think!&lt;/p&gt;</summary><category term="Book"></category><category term="third edition"></category><category term="Django"></category></entry><entry><title>Cosmic Python</title><link href="http://www.obeythetestinggoat.com/cosmic-python.html" rel="alternate"></link><published>2020-06-03T00:00:00+01:00</published><updated>2020-06-03T00:00:00+01:00</updated><author><name>Harry</name></author><id>tag:www.obeythetestinggoat.com,2020-06-03:cosmic-python.html</id><summary type="html">&lt;p&gt;Folks I've written a new book!&lt;/p&gt;
&lt;p&gt;Along with my coauthor &lt;a href="https://twitter.com/bob_the_mighty"&gt;Bob&lt;/a&gt;, we are proud
to release "Architecture Patterns with Python", which you can find out more
about at &lt;a href="https://www.cosmicpython.com"&gt;cosmicpython.com&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The cosmic soubriquet is a little joke, &lt;em&gt;Cosmos&lt;/em&gt; being the opposite of &lt;em&gt;Chaos&lt;/em&gt;
in ancient Greek, so we want to propose patterns to minimise chaos in your
applications.&lt;/p&gt;
&lt;p&gt;But the subtitle of the book is &lt;em&gt;Enabling TDD, DDD, and Event-Driven
Microservices&lt;/em&gt;, and the TDD part is relevant to this blog, and fans of the
Testing Goat.  In my two years at MADE and working with Bob, I've refined
some of my thinking and some of the ways I approach testing, and I think
if I were writing TTDwP again today, I might change the way I present some
things.&lt;/p&gt;
&lt;p&gt;In brief:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Mocking is not the only way to handle external (I/O et al) dependencies
  for your unit tests.  Other techniques are possible, and often offer
  major benefits&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you really want to get to a test pyramid (where unit tests outnumber
  slow/e2e/integration tests by an order of magnitude), then you'll probably
  need to make some specific design choices around identifying business logic
  and decoupling it from infrastructure code.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When deciding what kind of unit tests to write, there's a lot to be said for
  writing them at the highest level of abstraction possible.  It gives you more
  room to refactor later.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you're curious about those questions, head on over to &lt;a href="https://www.cosmicpython.com"&gt;cosmicpython.com&lt;/a&gt;,
and let me know what you think!&lt;/p&gt;</summary><category term="architecture"></category><category term="design"></category><category term="ddd"></category></entry><entry><title>New Book Excerpt: On Coupling and Abstractions</title><link href="http://www.obeythetestinggoat.com/new-book-excerpt-abstractions.html" rel="alternate"></link><published>2019-09-03T14:40:00+01:00</published><updated>2019-09-03T14:40:00+01:00</updated><author><name>Harry J.W. Percival</name></author><id>tag:www.obeythetestinggoat.com,2019-09-03:new-book-excerpt-abstractions.html</id><summary type="html">&lt;div id="preamble"&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;&lt;em&gt;At MADE.com i&amp;#8217;ve been working on a new book with the chief architect, Bob
Gregory, in which we talk about application architecture and design.  What
follows is an excerpt, a standalone chapter in which we talk about abstractions,
testability, and coupling.&lt;/em&gt;&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;&lt;em&gt;Have you ever struggled with writing tests for a bit of code that has a lot
of dependencies? Someting involving I/O, interacting with 3rd parties, things
like that?  Wondering whether end-to-end/integration tests are the only way,
and/or struggling with mocks that make tests hard to understand and evolve
over time?&lt;/em&gt;&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;&lt;em&gt;Most of the other chapters are about specific architectural patterns, but this
one kind of stands on its own. When Bob and I first talked about choosing the right
abstraction in order to enable testing, it was a real lightbulb moment for me, so
I&amp;#8217;m really interested in what people think&amp;#8201;&amp;#8212;&amp;#8201;does it help you?&lt;/em&gt;&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;&lt;em&gt;You can follow the progress on the new book at &lt;a href="https://github.com/python-leap/book/"&gt;https://github.com/python-leap/book/&lt;/a&gt;,
and you can read the Early Release edition on &lt;a href="https://learning.oreilly.com/library/view/enterprise-architecture-patterns/9781492052197/"&gt;Oreilly Safari&lt;/a&gt; (sign up for a free account if you
need one) (and apologies for the title, we&amp;#8217;re working hard to convince O&amp;#8217;Reilly to change it)&lt;/em&gt;&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;Allow us a brief digression on the subject of abstractions, dear reader.
We&amp;#8217;ve talked about &lt;em&gt;abstractions&lt;/em&gt; quite a lot.  The Repository is an
abstraction over permanent storage for example.  But what makes a good
abstraction?  What do we want from them?  And how do they relate to testing?&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_encapsulation_and_abstraction_in_general"&gt;Encapsulation and Abstraction, in General&lt;/h3&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;Take a look at the following two snippets of Python code, &lt;a href="#urllib_example"&gt;[urllib_example]&lt;/a&gt;
and &lt;a href="#requests_example"&gt;[requests_example]&lt;/a&gt;:&lt;/p&gt;&lt;/div&gt;
&lt;div class="exampleblock" id="urllib_example"&gt;
&lt;div class="title"&gt;Example 1. Do a search with urllib&lt;/div&gt;
&lt;div class="content"&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;urllib.request&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urlopen&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;urllib.parse&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urlencode&lt;/span&gt;

&lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Sausages&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;json&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;handle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;http://api.duckduckgo.com&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;?&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;urlencode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;raw_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;utf8&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;RelatedTopics&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Text&amp;#39;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;FirstURL&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39; - &amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Text&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;div class="exampleblock" id="requests_example"&gt;
&lt;div class="title"&gt;Example 2. Do a search with requests&lt;/div&gt;
&lt;div class="content"&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Sausages&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;json&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;http://api.duckduckgo.com/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;RelatedTopics&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Text&amp;#39;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;FirstURL&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39; - &amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Text&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;Both of these code listings do the same thing: they submit form-encoded values
to a URL in order to use a search engine API. But the second is simpler to read
and understand because it operates at a higher level of abstraction.&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;We can take this one step further still by identifying and naming the task we
want the code to perform for us, and use an even higher-level abstraction to make
it explicit:&lt;/p&gt;&lt;/div&gt;
&lt;div class="exampleblock" id="ddg_example"&gt;
&lt;div class="title"&gt;Example 3. Do a search with the duckduckgo module&lt;/div&gt;
&lt;div class="content"&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;duckduckgo&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;duckduckgo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Sausages&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39; - &amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;Encapsulating behavior using abstractions is a powerful tool for making
our code more expressive and easier to maintain.&lt;/p&gt;&lt;/div&gt;
&lt;div class="admonitionblock"&gt;
&lt;table&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;div class="title"&gt;Note&lt;/div&gt;
&lt;/td&gt;
&lt;td class="content"&gt;This approach is inspired by the OO practice of
    &lt;a href="http://www.wirfs-brock.com/Design.html"&gt;responsibility-driven design&lt;/a&gt;.
    which would use the words &lt;em&gt;roles&lt;/em&gt; and &lt;em&gt;responsibilities&lt;/em&gt; rather than tasks.
    The main point is to think about code in terms of behavior, rather than
    in terms of data or algorithms.  If you&amp;#8217;ve come across CRC cards, they&amp;#8217;re
    driving at the same thing.&lt;/td&gt;
&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;(In a traditional OO language you might use an abstract base class or an
interface to define an abstraction.  In Python you can (and we sometimes do)
use ABCs, but you can also rely on duck typing.  The abstraction can just mean,
"the public API of the thing you&amp;#8217;re using"; a function name plus some
arguments, for example.)&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;So far, so not-very-earth-shaking, you&amp;#8217;re probably thinking.  Abstraction
and encapsulation are nice, we all knew that.  So what?&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_abstractions_as_a_way_of_reducing_excessive_coupling"&gt;Abstractions as a Way of Reducing Excessive Coupling&lt;/h3&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;When we&amp;#8217;re writing code for fun, or in a
&lt;a href="http://www.peterprovost.org/blog/2012/05/02/kata-the-only-way-to-learn-tdd/"&gt;kata&lt;/a&gt;,
we get to play with ideas freely, hammering things out and refactoring
aggressively. In a large-scale system, though, we become constrained by the
decisions made elsewhere in the system.&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;When we&amp;#8217;re unable to change component A for fear of breaking component B, we say
that the components have become coupled. Locally, coupling is a good thing: it&amp;#8217;s
a sign that our code is working together, each component supporting the others,
fitting in place like the gears of a watch.  In jargon, we say this works when
there is high &lt;em&gt;cohesion&lt;/em&gt; between the coupled elements.&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;Globally, coupling is a nuisance: it increases the risk and the cost of changing
our code, sometimes to the point where we feel unable to make some changes at
all. This is the problem with the ball of mud pattern: as the application grows,
if we&amp;#8217;re unable to prevent coupling between elements that have no cohesion, that
coupling increases superlinearly until we are no longer able to effectively
change our systems.&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;We can reduce the degree of coupling within a system
(&lt;a href="#coupling_illustration1"&gt;[coupling_illustration1]&lt;/a&gt;) by abstracting away the details
(&lt;a href="#coupling_illustration2"&gt;[coupling_illustration2]&lt;/a&gt;):&lt;/p&gt;&lt;/div&gt;
&lt;div class="listingblock image-source" id="coupling_illustration1"&gt;
&lt;div class="title"&gt;Lots of coupling&lt;/div&gt;
&lt;div class="content"&gt;
&lt;pre&gt;&lt;code&gt;+--------+      +--------+
| System | ---&amp;gt; | System |
|   A    | ---&amp;gt; |   B    |
|        | ---&amp;gt; |        |
|        | ---&amp;gt; |        |
|        | ---&amp;gt; |        |
+--------+      +--------+&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;div class="listingblock image-source" id="coupling_illustration2"&gt;
&lt;div class="title"&gt;Less coupling&lt;/div&gt;
&lt;div class="content"&gt;
&lt;pre&gt;&lt;code&gt;+--------+                           +--------+
| System |      /-------------\      | System |
|   A    |      |             | ---&amp;gt; |   B    |
|        | ---&amp;gt; | Abstraction | ---&amp;gt; |        |
|        |      |             | ---&amp;gt; |        |
|        |      \-------------/      |        |
+--------+                           +--------+&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;In both diagrams, we have a pair of subsystems, with the one dependent on
the other. In the first diagram, there is a high degree of coupling between the
two because of reasons. If we need to change system B, there&amp;#8217;s a good
chance that the change will ripple through to system A.&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;In the second, though, we have reduced the degree of coupling by inserting a
new, simpler, abstraction. This abstraction serves to protect us from change by
hiding away the complex details of whatever system B does.&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;But how do we pick the right abstraction?  And what does this all have to
do with testing?&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_abstracting_state_aids_testability"&gt;Abstracting State Aids Testability&lt;/h3&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;Time to get into the nitty-gritty. Let&amp;#8217;s see an example. Imagine we want to
write some code for synchronising two file directories which we&amp;#8217;ll call the
source and the destination.&lt;/p&gt;&lt;/div&gt;
&lt;div class="ulist"&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;
If a file exists in the source, but not the destination, copy the file over.
&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;
If a file exists in the source, but has a different name than in the destination,
  rename the destination file to match.
&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;
If a file exists in the destination but not the source, remove it.
&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;Our first and third requirements are simple enough, we can just compare two
lists of paths. Our second is trickier, though. In order to detect renames,
we&amp;#8217;ll have to inspect the content of files. For this we can use a hashing
function like md5 or SHA. The code to generate a SHA hash from a file is simple
enough.&lt;/p&gt;&lt;/div&gt;
&lt;div class="exampleblock" id="hash_file"&gt;
&lt;div class="title"&gt;Example 4. Hashing a file (sync.py)&lt;/div&gt;
&lt;div class="content"&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;BLOCKSIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;65536&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hash_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;hasher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sha1&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rb&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;buf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BLOCKSIZE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;hasher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;buf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BLOCKSIZE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hasher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;Now we need to write the bit that makes decisions about what to do. When we
have to tackle a problem from first principles, we usually try to write a
simple implementation, and then refactor towards better design. We&amp;#8217;ll use this
approach throughout the book, because it&amp;#8217;s how we write code in the real world:
start with a solution to the smallest part of the problem, and then iteratively
make the solution richer and better designed.&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;Our first hackish approach looks something like this:&lt;/p&gt;&lt;/div&gt;
&lt;div class="exampleblock" id="sync_first_cut"&gt;
&lt;div class="title"&gt;Example 5. Basic sync algorithm (sync.py)&lt;/div&gt;
&lt;div class="content"&gt;
&lt;div class="listingblock non-head"&gt;
&lt;div class="content"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;hashlib&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;shutil&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Walk the source folder and build a dict of filenames and their hashes&lt;/span&gt;
    &lt;span class="n"&gt;source_hashes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;folder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;walk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;source_hashes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;hash_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;folder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt;

    &lt;span class="n"&gt;seen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# Keep track of the files we&amp;#39;ve found in the target&lt;/span&gt;

    &lt;span class="c1"&gt;# Walk the target folder and get the filenames and hashes&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;folder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;walk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;dest_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;folder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt;
            &lt;span class="n"&gt;dest_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hash_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dest_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dest_hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;# if there&amp;#39;s a file in target that&amp;#39;s not in source, delete it&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;dest_hash&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;source_hashes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;dest_path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="c1"&gt;# if there&amp;#39;s a file in target that has a different path in source,&lt;/span&gt;
            &lt;span class="c1"&gt;# move it to the correct path&lt;/span&gt;
            &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;dest_hash&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;source_hashes&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;source_hashes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;dest_hash&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
                &lt;span class="n"&gt;shutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dest_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;folder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;source_hashes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;dest_hash&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="c1"&gt;# for every file that appears in source but not target, copy the file to&lt;/span&gt;
    &lt;span class="c1"&gt;# the target&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;src_hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;source_hashes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;src_hash&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;shutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;Fantastic! We have some code and it &lt;em&gt;looks&lt;/em&gt; okay, but before we run it on our
hard drive, maybe we should test it?  How do we go about testing this sort of thing?&lt;/p&gt;&lt;/div&gt;
&lt;div class="exampleblock" id="ugly_sync_tests"&gt;
&lt;div class="title"&gt;Example 6. Some end-to-end tests (test_sync.py)&lt;/div&gt;
&lt;div class="content"&gt;
&lt;div class="listingblock non-head"&gt;
&lt;div class="content"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_when_a_file_exists_in_the_source_but_not_the_destination&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tempfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mkdtemp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;dest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tempfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mkdtemp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;I am a very useful file&amp;quot;&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;my-file&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;expected_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;  &lt;span class="s1"&gt;&amp;#39;my-file&amp;#39;&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;expected_path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;expected_path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;

    &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;shutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rmtree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;shutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rmtree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_when_a_file_has_been_renamed_in_the_source&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tempfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mkdtemp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;dest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tempfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mkdtemp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;I am a file that was renamed&amp;quot;&lt;/span&gt;
        &lt;span class="n"&gt;source_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;source-filename&amp;#39;&lt;/span&gt;
        &lt;span class="n"&gt;old_dest_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;dest-filename&amp;#39;&lt;/span&gt;
        &lt;span class="n"&gt;expected_dest_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;source-filename&amp;#39;&lt;/span&gt;
        &lt;span class="n"&gt;source_path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;old_dest_path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;old_dest_path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;expected_dest_path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;


    &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;shutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rmtree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;shutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rmtree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;Wowsers, that&amp;#8217;s a lot of setup for two very simple cases! The problem is that
our domain logic, "figure out the difference between two directories," is tightly
coupled to the IO code. We can&amp;#8217;t run our difference algorithm without calling
the pathlib, shutil, and hashlib modules.&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;Our high-level code is coupled to low-level details, and it&amp;#8217;s making life hard.
As the scenarios we consider get more complex, our tests will get more unwieldy.
We can definitely refactor these tests (some of the cleanup could go into pytest
fixtures for example) but as long as we&amp;#8217;re doing filesystem operations, they&amp;#8217;re
going to stay slow and hard to read and write.&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_choosing_the_right_abstraction_s"&gt;Choosing the right abstraction(s)&lt;/h3&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;What could we do to rewrite our code to make it more testable?&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;Firstly we need to think about what our code needs from the filesystem.
Reading through the code, there are really three distinct things happening.
We can think of these as three distinct &lt;em&gt;responsibilities&lt;/em&gt; that the code has.&lt;/p&gt;&lt;/div&gt;
&lt;div class="olist arabic"&gt;&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;
We interrogate the filesystem using &lt;code&gt;os.walk&lt;/code&gt; and determine hashes for a
   series of paths. This is actually very similar in both the source and the
   destination cases.
&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;
We decide a file is new, renamed, or redundant.
&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;
We copy, move, or delete, files to match the source.
&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;Remember that we want to find &lt;em&gt;simplifying abstractions&lt;/em&gt; for each of these
responsibilities. That will let us hide the messy details so that we can
focus on the interesting logic.&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;For (1) and (2), we&amp;#8217;ve already intuitively started using an abstraction, a
dictionary of hashes to paths, and you may already have been thinking, "why not
use build up a dictionary for the destination folder as well as the source,
then we just compare two dicts?"  That seems like a very nice way to abstract
the current state of the filesystem.&lt;/p&gt;&lt;/div&gt;
&lt;div class="literalblock"&gt;
&lt;div class="content"&gt;
&lt;pre&gt;&lt;code&gt;source_files = {'hash1': 'path1', 'hash2': 'path2'}
dest_files = {'hash1': 'path1', 'hash2': 'pathX'}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;What about moving from step (2) to step (3)?  How can we abstract out the
actual move/copy/delete filesystem interaction?&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;We&amp;#8217;re going to apply a trick here that we&amp;#8217;ll employ on a grand scale later in
the book. We&amp;#8217;re going to separate &lt;em&gt;what&lt;/em&gt; we want to do from &lt;em&gt;how&lt;/em&gt; to do it.
We&amp;#8217;re going to make our program output a list of commands that look like this:&lt;/p&gt;&lt;/div&gt;
&lt;div class="literalblock"&gt;
&lt;div class="content"&gt;
&lt;pre&gt;&lt;code&gt;("COPY", "sourcepath", "destpath"),
("MOVE", "old", "new"),&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;Now we could write tests that just use 2 filesystem dicts as inputs, and
expect lists of tuples of strings representing actions as outputs.&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;Instead of saying "given this actual filesystem, when I run my function,
check what actions have happened?" we say, "given this &lt;em&gt;abstraction&lt;/em&gt; of a filesystem,
what &lt;em&gt;abstraction&lt;/em&gt; of filesystem actions will happen?"&lt;/p&gt;&lt;/div&gt;
&lt;div class="exampleblock" id="better_tests"&gt;
&lt;div class="title"&gt;Example 7. Simplified inputs and outputs in our tests (test_sync.py)&lt;/div&gt;
&lt;div class="content"&gt;
&lt;div class="listingblock skip"&gt;
&lt;div class="content"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_when_a_file_exists_in_the_source_but_not_the_destination&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;src_hashes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;hash1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;fn1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;dst_hashes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="n"&gt;expected_actions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;COPY&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/src/fn1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/dst/fn1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="o"&gt;...&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_when_a_file_has_been_renamed_in_the_source&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;src_hashes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;hash1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;fn1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;dst_hashes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;hash1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;fn2&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;expected_actions&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;MOVE&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/dst/fn2&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/dst/fn1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_implementing_our_chosen_abstractions"&gt;Implementing our chosen abstractions&lt;/h3&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;That&amp;#8217;s all very well, but how do we &lt;em&gt;actually&lt;/em&gt; write those new
tests, and how do we change our implementation to make it all work?&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;Our goal is to isolate the clever part of our system, and to be able to test it
thoroughly without needing to set up a real filesystem. We&amp;#8217;ll create a "core"
of code that has no dependencies on external state, and then see how it responds
when we give it input from the outside world.&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;Let&amp;#8217;s start off by splitting the code up to separate the stateful parts from
the logic.&lt;/p&gt;&lt;/div&gt;
&lt;div class="exampleblock" id="three_parts"&gt;
&lt;div class="title"&gt;Example 8. Split our code into three  (sync.py)&lt;/div&gt;
&lt;div class="content"&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  &lt;span class="c1"&gt;#&lt;b&gt;&amp;lt;3&amp;gt;&lt;/b&gt;&lt;/span&gt;
    &lt;span class="c1"&gt;# imperative shell step 1, gather inputs&lt;/span&gt;
    &lt;span class="n"&gt;source_hashes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;read_paths_and_hashes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;dest_hashes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;read_paths_and_hashes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# step 2: call functional core&lt;/span&gt;
    &lt;span class="n"&gt;actions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;determine_actions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source_hashes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dest_hashes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# imperative shell step 3, apply outputs&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;paths&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;copy&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;shutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copyfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;move&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;shutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;delete&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read_paths_and_hashes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  &lt;span class="c1"&gt;#&lt;b&gt;&amp;lt;1&amp;gt;&lt;/b&gt;&lt;/span&gt;
    &lt;span class="n"&gt;hashes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;folder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;walk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;hashes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;hash_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;folder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hashes&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;determine_actions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src_hashes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dst_hashes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;src_folder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dst_folder&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  &lt;span class="c1"&gt;#&lt;b&gt;&amp;lt;2&amp;gt;&lt;/b&gt;&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;sha&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;src_hashes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sha&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;dst_hashes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;sourcepath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src_folder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;
            &lt;span class="n"&gt;destpath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dst_folder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;copy&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sourcepath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;destpath&lt;/span&gt;

        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;dst_hashes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;sha&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;olddestpath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dst_folder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;dst_hashes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;sha&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="n"&gt;newdestpath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dst_folder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;move&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;olddestpath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newdestpath&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;sha&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;dst_hashes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sha&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;src_hashes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;delete&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dst_folder&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;div class="colist arabic"&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;
The code to build up the dictionary of paths and hashes is now trivially
    easy to write.
&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;
The core of our "business logic," which says, "given these two sets of
    hashes and filenames, what should we copy/move/delete?"  takes simple
    data structures and returns simple data structures.
&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;
And our top-level module now contains almost no logic whatseover, it&amp;#8217;s
    just an imperative series of steps: gather inputs, call our logic,
    apply outputs.
&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;Our tests now act directly on the &lt;code&gt;determine_actions()&lt;/code&gt; function:&lt;/p&gt;&lt;/div&gt;
&lt;div class="exampleblock" id="harry_tests"&gt;
&lt;div class="title"&gt;Example 9. Nicer looking tests (test_sync.py)&lt;/div&gt;
&lt;div class="content"&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;    &lt;span class="nd"&gt;@staticmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_when_a_file_exists_in_the_source_but_not_the_destination&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;src_hashes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;hash1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;fn1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;dst_hashes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="n"&gt;actions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;determine_actions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src_hashes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dst_hashes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/src&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/dst&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;actions&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;copy&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/src/fn1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/dst/fn1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;

    &lt;span class="nd"&gt;@staticmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_when_a_file_has_been_renamed_in_the_source&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;src_hashes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;hash1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;fn1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;dst_hashes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;hash1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;fn2&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;actions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;determine_actions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src_hashes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dst_hashes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/src&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/dst&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;actions&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;move&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/dst/fn2&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/dst/fn1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;Because we&amp;#8217;ve disentangled the logic of our program - the code for identifying
changes - from the low-level details of IO, we can easily test the core of our code.&lt;/p&gt;&lt;/div&gt;
&lt;div class="sect3"&gt;
&lt;h4 id="_testing_edge_to_edge_with_fakes"&gt;Testing Edge-to-Edge with Fakes&lt;/h4&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;When we start writing a new system, we often focus on the core logic first,
driving it with direct unit tests. At some point, though, we want to test bigger
chunks of the system together.&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;We &lt;em&gt;could&lt;/em&gt; return to our end-to-end tests, but those are still as tricky to
write and maintain as before. Instead, we often write tests that invoke a whole
system together, but fake the IO, sort of &lt;em&gt;edge-to-edge&lt;/em&gt;.&lt;/p&gt;&lt;/div&gt;
&lt;div class="exampleblock" id="di_version"&gt;
&lt;div class="title"&gt;Example 10. Explicit dependencies (sync.py)&lt;/div&gt;
&lt;div class="content"&gt;
&lt;div class="listingblock skip"&gt;
&lt;div class="content"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;synchronise_dirs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filesystem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;source_root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dest_root&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="c1"&gt;#&lt;b&gt;&amp;lt;1&amp;gt;&lt;/b&gt;&lt;/span&gt;

    &lt;span class="n"&gt;source_hashes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source_root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;#&lt;b&gt;&amp;lt;2&amp;gt;&lt;/b&gt;&lt;/span&gt;
    &lt;span class="n"&gt;dest_hashes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dest_root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;sha&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;src_hashes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sha&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;dst_hashes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;sourcepath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;source_root&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;
            &lt;span class="n"&gt;destpath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dest_root&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;
            &lt;span class="n"&gt;filesystem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;destpath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sourcepath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;#&lt;b&gt;&amp;lt;3&amp;gt;&lt;/b&gt;&lt;/span&gt;

        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;dst_hashes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;sha&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;olddestpath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dest_root&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;dst_hashes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;sha&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="n"&gt;newdestpath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dest_root&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;
            &lt;span class="n"&gt;filesystem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;oldestpath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newdestpath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;sha&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;dst_hashes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sha&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;src_hashes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;filesystem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;del&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dest_root&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;div class="colist arabic"&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;
Our top-level function now exposes two new dependencies, a &lt;code&gt;reader&lt;/code&gt; and a
    &lt;code&gt;filesystem&lt;/code&gt;
&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;
We invoke the &lt;code&gt;reader&lt;/code&gt; to produce our files dict.
&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;
And we invoke the &lt;code&gt;filesystem&lt;/code&gt; to apply the changes we detect.
&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;
&lt;div class="admonitionblock"&gt;
&lt;table&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;div class="title"&gt;Tip&lt;/div&gt;
&lt;/td&gt;
&lt;td class="content"&gt;Notice that, although we&amp;#8217;re using dependency injection, there was no need
    to define an abstract base class or any kind of explicit interface.  In the
    book we often show ABCs because we hope they help to understand what the
    abstraction is, but they&amp;#8217;re not necessary.  Python&amp;#8217;s dynamic nature means
    we can always rely on duck typing.&lt;/td&gt;
&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="exampleblock" id="bob_tests"&gt;
&lt;div class="title"&gt;Example 11. Tests using DI&lt;/div&gt;
&lt;div class="content"&gt;
&lt;div class="listingblock skip"&gt;
&lt;div class="content"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FakeFileSystem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="c1"&gt;#&lt;b&gt;&amp;lt;1&amp;gt;&lt;/b&gt;&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="c1"&gt;#&lt;b&gt;&amp;lt;2&amp;gt;&lt;/b&gt;&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;COPY&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;MOVE&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;DELETE&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_when_a_file_exists_in_the_source_but_not_the_destination&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sha1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;my-file&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;dest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;filesystem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FakeFileSystem&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/source&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/dest&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;synchronise_dirs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filesystem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/source&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/dest&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;filesystem&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;COPY&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/source/my-file&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/dest/my-file&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_when_a_file_has_been_renamed_in_the_source&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sha1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;renamed-file&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;dest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sha1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;original-file&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;filesystem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FakeFileSystem&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/source&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/dest&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;synchronise_dirs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filesystem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/source&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/dest&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;filesystem&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;MOVE&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/dest/original-file&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/dest/renamed-file&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;div class="colist arabic"&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;
Bob &lt;em&gt;loves&lt;/em&gt; using lists to build simple test doubles, even though his
    co-workers get mad. It means we can write tests like
    &lt;code&gt;assert &lt;em&gt;foo&lt;/em&gt; not in database&lt;/code&gt;
&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;
Each method in our &lt;code&gt;FakeFileSystem&lt;/code&gt; just appends something to the list so we
    can inspect it later. This is an example of a Spy Object.
&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;The advantage of this approach is that your tests act on the exact same function
that&amp;#8217;s used by your production code.  The disadvantage is that we have to make
our stateful components explicit and we have to pass them around. DHH famously
described this as "test damage".&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;In either case, we can now work on fixing all the bugs in our implementation;
enumerating tests for all the edge cases is now much easier.&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect3"&gt;
&lt;h4 id="_why_not_just_patch_it_out"&gt;Why Not Just Patch It Out?&lt;/h4&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;At this point some of our readers will be scratching their heads and thinking
"Why don&amp;#8217;t you just use &lt;code&gt;mock.patch&lt;/code&gt; and save yourself the effort?&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;We avoid using mocks in this book, and in our production code, too. We&amp;#8217;re not
going to enter into a Holy War, but our instinct is that mocking frameworks are
a code smell.&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;Instead, we like to clearly identify the responsibilities in our codebase, and to
separate those responsibilities out into small, focused objects that are easy to
replace with a test double.&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;There&amp;#8217;s a few, closely related reasons for that:&lt;/p&gt;&lt;/div&gt;
&lt;div class="olist arabic"&gt;&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;
Patching out the dependency you&amp;#8217;re using makes it possible to unit test the
code, but it does nothing to improve the design. Using mock.patch won&amp;#8217;t let your
code work with a &lt;code&gt;--dry-run&lt;/code&gt; flag, nor will it help you run against an ftp
server. For that, you&amp;#8217;ll need to introduce abstractions.
&lt;/p&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;Designing for testability really means designing for extensibility. We trade off
a little more complexity for a cleaner design that admits novel use-cases.&lt;/p&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;
Tests that use mocks &lt;em&gt;tend&lt;/em&gt; to be more coupled to the implementation details
of the codebase. That&amp;#8217;s because mock tests verify the interactions between
things: did I call &lt;code&gt;shutil.copy&lt;/code&gt; with the right arguments? This coupling between
code and test &lt;em&gt;tends&lt;/em&gt; to make tests more brittle in our experience.
&lt;/p&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;Martin Fowler wrote about this in his 2007 blog post
&lt;a href="https://www.martinfowler.com/articles/mocksArentStubs.html"&gt;Mocks Aren&amp;#8217;t Stubs&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;
Over-use of mocks leads to complicated test suites that fail to explain the
code.
&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;We view TDD as a design practice first, and a testing practice second. The tests
act as a record of our design choices, and serve to explain the system to us
when we return to the code after a long absence.&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;Tests that use too many mocks get overwhelmed with setup code that hides the
story we care about.&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;Steve Freeman has a great example of over-mocked tests in his talk
&lt;a href="https://www.youtube.com/watch?v=B48Exq57Zg8"&gt;Test Driven Development: That&amp;#8217;s Not What We Meant&lt;/a&gt;.&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;You should also check out this &lt;a href="https://www.youtube.com/watch?v=Ldlz4V-UCFw"&gt;Pycon talk on Mocking Pitfalls&lt;/a&gt;
by our esteemed tech reviewer, Ed Jung, which addresses these issues directly.&lt;/p&gt;&lt;/div&gt;
&lt;div class="sidebarblock"&gt;
&lt;div class="content"&gt;
&lt;div class="title"&gt;So Which Do We Use in this Book? FCIS or DI?&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;Both. Our domain model is entirely free of dependencies and side-effects,
so that&amp;#8217;s our functional core.  The service layer that we build around it
(in &lt;a href="#chapter_04_service_layer"&gt;[chapter_04_service_layer]&lt;/a&gt;) allows us to drive the system edge-to-edge
and we use dependency injection to provide those services with stateful
components, so we can still unit test them.&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;See &lt;a href="#chapter_12_dependency_injection"&gt;[chapter_12_dependency_injection]&lt;/a&gt; for more exploration of making our
dependency injection more explicit and centralised.&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_wrap_up_depend_on_abstractions"&gt;Wrap-up: "Depend on Abstractions."&lt;/h3&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;We&amp;#8217;ll see this idea come up again and again in the book: we can make our
systems easier to test and maintain by simplifying the interface between our
business logic and messy IO. Finding the right abstraction is tricky, but here&amp;#8217;s
a few heuristics and questions to ask yourself:&lt;/p&gt;&lt;/div&gt;
&lt;div class="ulist"&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;
Can I choose a familiar Python datastructure to represent the state of the
  messy system, and try to imagine a single function that can return that
  state?
&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;div class="ulist"&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;
Where can I draw a line between my systems, where can I carve out a seam, to
  stick that abstraction in?
&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;div class="ulist"&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;
What are the dependencies and what is the core "business" logic?
&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;Practice makes less-imperfect!&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;And now back to our regular programming&amp;#8230;&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
</summary><category term="new book"></category><category term="abstractions"></category><category term="oo"></category><category term="design"></category></entry><entry><title>Python Architecture Stuff: do we need more?</title><link href="http://www.obeythetestinggoat.com/python-architecture-stuff-do-we-need-more.html" rel="alternate"></link><published>2019-02-11T15:28:00+00:00</published><updated>2019-02-11T15:28:00+00:00</updated><author><name>Harry</name></author><id>tag:www.obeythetestinggoat.com,2019-02-11:python-architecture-stuff-do-we-need-more.html</id><summary type="html">&lt;p&gt;I've been learning lots of new stuff about application architecture recently,
and I want to get a sense from you, dear reader, of whether these ideas are
of interest, and whether we should try and build some more resources 
(blogs, talks, etc) around it.&lt;/p&gt;
&lt;h2&gt;How should we structure an application to get the most out of our tests?&lt;/h2&gt;
&lt;p&gt;For me it all started with this question. 
At the &lt;a href="https://www.obeythetestinggoat.com/book/chapter_hot_lava.html"&gt;end of my book&lt;/a&gt;
I concluded on a chapter discussing how to get the most out of your tests, on
the tradeoffs between unit, integration and end-to-end tests, and made some
vague, flailing gestures towards topics I didn't really understand: &lt;em&gt;ports and
adapters&lt;/em&gt;, &lt;em&gt;hexagonal architecture&lt;/em&gt;, &lt;em&gt;functional core imperative shell&lt;/em&gt;, the
&lt;em&gt;clean architecture&lt;/em&gt;, and so on.&lt;/p&gt;
&lt;p&gt;Since then I've managed to fall in with a &lt;a href="https://io.made.com/"&gt;tech team&lt;/a&gt;
that are actively implementing these sorts of patterns, in Python.  And the
thing is, these architectural patterns are nothing new, people have been
exploring them for years in the world of Java and C#.  They were just new to
me... and I may be over-reaching from my own experience here (I'm interested
in your reaction), but they are perhaps new to the Python community in general?&lt;/p&gt;
&lt;p&gt;It does feel like, as we mature, more and more of what were once small projects
and plucky startups turn into complex business and (whisper it) enterprise
software, so this stuff is likely to get more and more salient.&lt;/p&gt;
&lt;p&gt;I came to it initially from the angle of testing, and the right kind of
architecture really can help you to get the most out of your tests, by
separating out a core of business logic (the "domain model") and freeing it
from all infrastructure dependencies, allowing it to be tested entirely through
fast, flexible unit tests.  At Made it finally felt like the 
&lt;a href="https://martinfowler.com/articles/practical-test-pyramid.html"&gt;test pyramid&lt;/a&gt; was
an achievable goal rather than an impossible aspiration.&lt;/p&gt;
&lt;h2&gt;Classic books on the topic (All Java.)&lt;/h2&gt;
&lt;p&gt;The classic books (&lt;a href="https://domainlanguage.com/ddd/"&gt;Evans on DDD&lt;/a&gt; and 
&lt;a href="https://www.martinfowler.com/books/eaa.html"&gt;Fowler on Architecture Patterns&lt;/a&gt;,
are classic, and anyone with an interest in this stuff should read them, but
if you're anything like me, then wading through all that &lt;code&gt;public static void
main AbstractFactoryManager&lt;/code&gt; gubbins is a bit wearing.  Maybe some more
lightweight, Pythonic intros would make it all feel a bit more plausible,
a bit less enterprise-architecture-astronaut-ey?&lt;/p&gt;
&lt;h2&gt;Some existing resources in the Python world:&lt;/h2&gt;
&lt;p&gt;Made's chief architect, the Venerable Bob, has written a 4-part blog series
on the way we do things here, which I really enjoyed when I first started.
They're a quick, practical intro to the basic concepts of DDD, ports and
adapters / dependency inversion, and to some extent, event-driven architecture.
And all in Python.  (trigger warning: type hints).&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://io.made.com/introducing-command-handler/"&gt;Ports and Adapters with Command Handler pattern in Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://io.made.com/repository-and-unit-of-work-pattern-in-python/"&gt;Repository and Unit of Work Pattern in Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://io.made.com/commands-and-queries-handlers-and-views/"&gt;Commands and Queries, Handlers and Views&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://io.made.com/why-use-domain-events/"&gt;Why use Domain Events?&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;There's more on io.made.com but those are the main four.  We'd love to
get some feedback on them, what's covered well, what could do with more
explanation, and so on...&lt;/p&gt;
&lt;p&gt;And: a very timely release from last Christmas, check out
&lt;a href="https://leanpub.com/clean-architectures-in-python"&gt;Clean Architectures in Python&lt;/a&gt;
by Leonardo Giordani.  It's really two  books in one, part one being an intro
to TDD, but part 2 has four chapters introducing very similar patterns to the
ones I'm talking about here.&lt;/p&gt;
&lt;p&gt;I also enjoyed a talk from about a year ago by David Seddon 
&lt;a href="http://seddonym.me/talks/2017-12-12-rocky-river/"&gt;The Rocky River, how to architect your Django monolith&lt;/a&gt;,
showing someone else starting to think about how we go beyond the basic Django models/views/templates architecture.&lt;/p&gt;
&lt;p&gt;There's lots more at this &lt;a href="https://github.com/valignatev/ddd-dynamic"&gt;listing of DDD resources&lt;/a&gt;
by Valentin Ignatev, which I came across recently on twitter.  It feels like something is in the air.&lt;/p&gt;
&lt;h2&gt;Call to action:  is this stuff interesting?&lt;/h2&gt;
&lt;p&gt;Bob's already had some good feedback to his blog posts, and Leonardo has had some good initial sales,
so I'm sensing some interest from the Python community out there, but I'd like to sanity-check it.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Does this stuff sound interesting or relevant? Do you want to hear more?&lt;/li&gt;
&lt;li&gt;Are you doing Python stuff that's getting beyond the bounds of "basic webapp development" or "data pipeline"? Are you finding it difficult to write fast unit tests? Are you starting to want to disentangle your business logic from whichever framework you use?&lt;/li&gt;
&lt;li&gt;Are you doing DDD or using any of these classic patterns with Python already? Do you maybe have all the answers and want to tell me about it?  Or maybe just some answers and things that have worked well for you?&lt;/li&gt;
&lt;li&gt;Do you think this stuff all sounds hella abstract and pointless? Maybe Made.com is a bit of an outliner in the Python world, in that we're writing logistics/ERP/enterprisey software in Python, and it all feels very different from what you do day-to-day?&lt;/li&gt;
&lt;li&gt;What do you think the Python / dynamic languages community would most benefit from in terms of new guides to these topics?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I'd love to hear from you. Comments are open, or &lt;a href="https://twitter.com/hjwp"&gt;hmu on twitter, @hjwp&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Did I say read the classics?   Read the classics.&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Patterns of Enterprise Architecture by Martin Fowler &lt;a href="https://amzn.to/2U6HTZN"&gt;amazon.com&lt;/a&gt; / &lt;a href="https://amzn.to/2R0WkN3"&gt;.co.uk&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Domain-Driven Design by Eric Evans &lt;a href="https://amzn.to/2W9nANe"&gt;amazon.com&lt;/a&gt; / &lt;a href="https://amzn.to/2B7vmOP"&gt;.co.uk&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</summary><category term="architecture"></category><category term="design"></category><category term="ddd"></category></entry><entry><title>A Pytest pattern: using "parametrize" to customise nested fixtures.</title><link href="http://www.obeythetestinggoat.com/a-pytest-pattern-using-parametrize-to-customise-nested-fixtures.html" rel="alternate"></link><published>2018-10-19T14:50:00+01:00</published><updated>2018-10-19T14:50:00+01:00</updated><author><name>Harry</name></author><id>tag:www.obeythetestinggoat.com,2018-10-19:a-pytest-pattern-using-parametrize-to-customise-nested-fixtures.html</id><summary type="html">&lt;h2&gt;The problem: customisable fixtures in pytest&lt;/h2&gt;
&lt;p&gt;Let's say you're running along merrily with some fixtures that create database objects for you:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nd"&gt;@pytest.fixture&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;supplier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Supplier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;random_ref&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;random_name&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;US&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;



&lt;span class="nd"&gt;@pytest.fixture&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;supplier&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;random_ref&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;random_name&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;supplier&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;supplier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;net_price&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;9.99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And now you're writing a new test and you suddenly realise you need to customise your default "supplier" fixture:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_US_supplier_has_total_price_equal_net_price&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_price&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;net_price&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_EU_supplier_has_total_price_including_VAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;supplier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;supplier&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;FR&amp;quot;&lt;/span&gt; &lt;span class="c1"&gt;# oh, this doesn&amp;#39;t work&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_price&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;net_price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.2&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;For whatever reason, maybe because you need to set the &lt;code&gt;supplier.country&lt;/code&gt; before you add things to the DB, or before you instantiate product objects, you need to be able to adjust the &lt;code&gt;country&lt;/code&gt; field on your supplier feature.&lt;/p&gt;
&lt;h2&gt;Option 1: more fixtures&lt;/h2&gt;
&lt;p&gt;We can just create more fixtures, and try do do a bit of DRY by extracting out common logic:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_default_supplier&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Supplier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;random_ref&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;random_name&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@pytest.fixture&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;us_supplier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_default_supplier&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;US&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@pytest.fixture&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;eu_supplier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_default_supplier&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;FR&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;That's just one way you could do it, maybe you can figure out ways to reduce the duplication of the &lt;code&gt;db.add()&lt;/code&gt; stuff as well, but you are going to have to have a different, named fixture for each customisation of &lt;code&gt;Supplier&lt;/code&gt;, and eventually you may decide that doesn't scale.  &lt;code&gt;us_supplier&lt;/code&gt;, &lt;code&gt;eu_supplier&lt;/code&gt;, &lt;code&gt;asia_supplier&lt;/code&gt;, &lt;code&gt;ch_supplier&lt;/code&gt;, etc etc, too many fixtures!  I'd like just one, customisable fixture please.&lt;/p&gt;
&lt;h2&gt;Option 2: factory fixtures&lt;/h2&gt;
&lt;p&gt;Instead of a fixture returning an object directly, it can return a function that creates an object, and that function can take arguments:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nd"&gt;@pytest.fixture&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;make_supplier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Supplier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;random_ref&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;random_name&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_make_supplier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt;
        &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;

    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;_make_supplier&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The problem with this is that, once you start, you tend to have to go all the way, and make &lt;em&gt;all&lt;/em&gt; of your fixture hierarchy into factory functions: &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_EU_supplier_has_total_price_including_VAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;make_supplier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;supplier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;make_supplier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;FR&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;supplier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;supplier&lt;/span&gt; &lt;span class="c1"&gt;# OH, now this doesn&amp;#39;t work, because it&amp;#39;s too late again&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_price&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;net_price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.2&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And so...&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nd"&gt;@pytest.fixture&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;make_product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;random_ref&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;random_name&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_make_product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;supplier&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;supplier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;supplier&lt;/span&gt;
        &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;

    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;_make_product&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_EU_supplier_has_total_price_including_VAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;make_supplier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;make_product&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;supplier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;make_supplier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;FR&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;make_product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;supplier&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;supplier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_price&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;net_price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.2&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;That works, but firstly now everything is a factory-fixture, which makes them more convoluted, and secondly, your tests are filling up with extra calls to &lt;code&gt;make_things&lt;/code&gt;, and you're having to embed some of the domain knowledge of what-depends-on-what into your tests as well as your fixtures.&lt;/p&gt;
&lt;h2&gt;Option 3: "normal" fixture parametrization&lt;/h2&gt;
&lt;p&gt;This is a pretty cool feature of Pytest.  You probably already know that you can parametrize tests, injecting different values for arguments to your test and then running the same test multiple times, once for each value:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nd"&gt;@pytest.mark.parametrize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;n&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_doubling&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="c1"&gt;# will pass twice and fail once&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;A slightly less well-known feature is that you can parametrize fixtures as well.  You need to use the special &lt;code&gt;request&lt;/code&gt; fixture to access your parameters:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nd"&gt;@pytest.fixture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;US&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;FR&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;supplier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Supplier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;random_ref&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;random_name&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;param&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now any test that depends on &lt;code&gt;supplier&lt;/code&gt;, directly or indirectly, will be run twice, once with &lt;code&gt;supplier.country = US&lt;/code&gt; and once with &lt;code&gt;FR&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That's really cool for checking that a given piece of logic works in a variety of different cases, but it's not really ideal in our case.  We have to build a bunch of &lt;code&gt;if&lt;/code&gt; logic into our tests:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_US_supplier_has_no_VAT_but_EU_supplier_has_total_price_including_VAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# this test is magically run twice, but:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;supplier&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;US&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_price&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;net_price&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;supplier&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;FR&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_price&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;net_price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.2&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;So that's ugly, and on top of that, now &lt;em&gt;every single&lt;/em&gt; test that depends (indirectly) on supplier gets run twice, and some of those extra test runs may be totally irrelevant to what the country is.&lt;/p&gt;
&lt;h2&gt;Presenting: using pytest parametrization to override nested default-value fixtures&lt;/h2&gt;
&lt;p&gt;We introduce an extra fixture that holds a default value for the &lt;code&gt;country&lt;/code&gt; field:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nd"&gt;@pytest.fixture&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;country&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;US&amp;quot;&lt;/span&gt;


&lt;span class="nd"&gt;@pytest.fixture&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;supplier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Supplier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;random_ref&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;random_name&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And then in the tests that need to change it, we can use &lt;code&gt;parametrize&lt;/code&gt; to override the default value of &lt;code&gt;country&lt;/code&gt;, &lt;em&gt;even though the country fixture isn't explicitly named in that test&lt;/em&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nd"&gt;@pytest.mark.parametrize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;country&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;US&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_US_supplier_has_total_price_equal_net_price&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_price&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;net_price&lt;/span&gt;

&lt;span class="nd"&gt;@pytest.mark.parametrize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;country&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;EU&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_EU_supplier_has_total_price_including_VAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_price&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;net_price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.2&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Amazing huh?  The only problem is that you're now likely to build a teetering tower of implicit dependencies where the only way to find out what's actually happening is to spend ages spelunking in &lt;code&gt;conftest.py&lt;/code&gt;, but, hey, if you didn't like crazy nested fixture magic, why are you using pytest in the first place, right?&lt;/p&gt;
&lt;p&gt;Reactions and alternative suggestions on a postcard please :)&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;small&gt;&lt;i&gt;
This blog post inspired by a pattern I first explored at &lt;a href="https://www.pythonanywhere.com/"&gt;PythonAnywhere&lt;/a&gt;, which came up again recently; I found myself writing two successive answers to
&lt;a href="https://stackoverflow.com/questions/42228895/how-to-parametrize-a-pytest-fixture"&gt;this SO post&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Cross-posted at the &lt;a href="https://io.made.com/a-pytest-pattern-using-parametrize-to-customise-fixtures/"&gt;made.com dev blog&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Code samples &lt;a href="https://github.com/hjwp/www.obeythetestinggoat.com/tree/master/example-code/pytest_overriding_nested_fixtures_pattern"&gt;can be found here&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;/i&gt;&lt;/small&gt;&lt;/p&gt;</summary><category term="pytest"></category><category term="fixtures"></category></entry><entry><title>Speeding up Django unit tests with SQLite, keepdb and /dev/shm</title><link href="http://www.obeythetestinggoat.com/speeding-up-django-unit-tests-with-sqlite-keepdb-and-devshm.html" rel="alternate"></link><published>2017-11-23T18:32:00+00:00</published><updated>2017-11-23T18:32:00+00:00</updated><author><name>Harry</name></author><id>tag:www.obeythetestinggoat.com,2017-11-23:speeding-up-django-unit-tests-with-sqlite-keepdb-and-devshm.html</id><summary type="html">&lt;p&gt;Here's the tldr version:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;DATABASES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;default&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;ENGINE&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;your db settings as normal&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;TEST&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;# this gets you in-memory sqlite for tests, which is fast&lt;/span&gt;
          &lt;span class="s1"&gt;&amp;#39;ENGINE&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;django.db.backends.sqlite3&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;test&amp;#39;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;keepdb&amp;#39;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# and this allows you to use --keepdb to skip re-creating the db,&lt;/span&gt;
    &lt;span class="c1"&gt;# even faster!&lt;/span&gt;
    &lt;span class="n"&gt;DATABASES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;default&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;TEST&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;NAME&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/dev/shm/myproject.test.db.sqlite3&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h2&gt;More context&lt;/h2&gt;
&lt;p&gt;For day-to-day development, running your tests needs to be as fast as possible to
keep you in a good workflow.  Although you're unlikely to be using SQLite as your
production database, using it for tests in dev is often a nice shortcut, particularly
since Django will use an in-memory sqlite database for tests, which is even
faster than one on disk.&lt;/p&gt;
&lt;p&gt;But, especially if you have a large and complicated database, re-creating it with each
test run can take quite a bit of time. That's where the &lt;code&gt;--keepdb&lt;/code&gt; step comes in.  Of
course, normally if you're using an in-memory database, &lt;code&gt;keepdb&lt;/code&gt; doesn't make
any sense because memory disappears between runs.  That's where the sneaky
trick of using &lt;em&gt;/dev/shm&lt;/em&gt; comes in.  In linux, &lt;em&gt;/dev/shm&lt;/em&gt; is actually a
filesystem against your machine's RAM, and it will persist between processes,
until you reboot your machine.&lt;/p&gt;
&lt;p&gt;So you get all the speed of an in-memory SQLite database, with the extra boost of not
having to re-create the database.&lt;/p&gt;
&lt;h2&gt;What if the database changes,&lt;/h2&gt;
&lt;p&gt;... I hear you ask?  Django is smart enough to apply any new migrations to the
&lt;code&gt;keepdb&lt;/code&gt; database if it notices them.
&lt;a href="https://docs.djangoproject.com/en/1.11/ref/django-admin/#cmdoption-test-keepdb"&gt;Docs here&lt;/a&gt;.
This works pretty well in my experience, although I have had to blow away the test
db in &lt;em&gt;/dev/shm&lt;/em&gt; manually once or twice...&lt;/p&gt;
&lt;h2&gt;Don't do this in CI&lt;/h2&gt;
&lt;p&gt;But I'm only advocating this for use in development!  Ultimately, Postgres or whichever
database you're using will behave differently from SQLite.  Django does a good job of
abstracting away 90% of those differences, but that still leaves plenty of strange edge
case behaviours to do with default values, ordering and transactions that can easily trip
you up.&lt;/p&gt;
&lt;p&gt;Make sure you always run your test suite in CI against the real database.&lt;/p&gt;
&lt;p&gt;You could use an environment variable for example, to make sure:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;CI&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;del&lt;/span&gt; &lt;span class="n"&gt;DATABASES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;default&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;TEST&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h2&gt;Some numbers:&lt;/h2&gt;
&lt;p&gt;Here's a subset of the PythonAnywhere tests running, first without keepdb:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;»»»» time ./manage.py test console
Creating test database for alias &amp;#39;default&amp;#39;...
................................................................................
................................................................................
...........................................................
----------------------------------------------------------------------
Ran 219 tests in 5.261s

OK
Destroying test database for alias &amp;#39;default&amp;#39;...
10.86user 0.25system 0:11.12elapsed 99%CPU 
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And now with keepdb:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;»»»» time ./manage.py test console --keepdb
Using existing test database for alias &amp;#39;default&amp;#39;...
................................................................................
................................................................................
...........................................................
----------------------------------------------------------------------
Ran 219 tests in 5.557s

OK
Preserving test database for alias &amp;#39;default&amp;#39;...
6.28user 0.36system 0:06.66elapsed 99%CPU 
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Notice the time that Django reports is the almost the same in both cases, but the
actual elapsed time is quite different -- that's because Django isn't counting time
spent re-creating the database at the beginning of the test run.&lt;/p&gt;
&lt;p&gt;(also, be aware if you're running your own tests here that you will only see an
improvement the &lt;em&gt;second&lt;/em&gt; time you run with &lt;code&gt;keepdb&lt;/code&gt;.  On the first run it's still
creating the database)&lt;/p&gt;
&lt;h2&gt;Your mileage may vary&lt;/h2&gt;
&lt;p&gt;On a different project with simpler models I see very different results:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;»»»» time ./manage.py test opera.tests.test_pages
[...]
Ran 65 tests in 7.620s
15.44user 0.08system 0:15.53elapsed 99%CPU

»»»» time ./manage.py test opera.tests.test_pages --keepdb
[...]
Ran 65 tests in 7.535s
14.91user 0.10system 0:15.02elapsed 99%CPU
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Really not that much in it!  Although that's on my laptop with a nice fast
processor and SSD. Differences are more pronounced (on both projects) on a
system with a slower CPU and filesystem:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ &lt;span class="nb"&gt;time&lt;/span&gt; ./manage.py &lt;span class="nb"&gt;test&lt;/span&gt; opera.tests.test_pages
Creating &lt;span class="nb"&gt;test&lt;/span&gt; database &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nb"&gt;alias&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;default&amp;#39;&lt;/span&gt;...
&lt;span class="o"&gt;[&lt;/span&gt;...&lt;span class="o"&gt;]&lt;/span&gt;
Ran &lt;span class="m"&gt;65&lt;/span&gt; tests in 13.620s
real    0m25.720s

$ &lt;span class="nb"&gt;time&lt;/span&gt; ./manage.py &lt;span class="nb"&gt;test&lt;/span&gt; opera.tests.test_pages --keepdb
Using existing &lt;span class="nb"&gt;test&lt;/span&gt; database &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nb"&gt;alias&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;default&amp;#39;&lt;/span&gt;...
&lt;span class="o"&gt;[&lt;/span&gt;...&lt;span class="o"&gt;]&lt;/span&gt;
Ran &lt;span class="m"&gt;65&lt;/span&gt; tests in 10.648s
real    0m20.632s
&lt;/pre&gt;&lt;/div&gt;


&lt;h2&gt;Linux only!&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;/dev/shm&lt;/em&gt; only exists on Unixey operating systems. A bit of googling might help you
find alternatives on Windows and MacOS though -- I can't vouch for these, but here are
the first two links I found while duckduckgoing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;https://stackoverflow.com/questions/2033362/does-os-x-have-an-equivalent-to-dev-shm#2033417 &lt;/li&gt;
&lt;li&gt;https://stackoverflow.com/questions/3011464/what-is-the-dev-shm-equivalence-in-windows-system &lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;More tips&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Changing the password hashing algorithm (for tests only) can speed up user creation.
  &lt;a href="http://www.daveoncode.com/2013/09/23/effective-tdd-tricks-to-speed-up-django-tests-up-to-10x-faster/"&gt;This, and more tips, here&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you really want to use Postgres on your dev machine, you can also 
  &lt;a href="https://stackoverflow.com/questions/9407442/optimise-postgresql-for-fast-testing"&gt;hack your postgres config to speed things up&lt;/a&gt;,
  although, again, you'd want to make sure your config in CI was more
  (exactly?) like production.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let me know if these help you!&lt;/p&gt;</summary><category term="Django"></category><category term="Tests"></category><category term="Sqlite"></category></entry><entry><title>Second edition is out! Reviews please!</title><link href="http://www.obeythetestinggoat.com/second-edition-is-out-reviews-please.html" rel="alternate"></link><published>2017-09-12T13:52:00+01:00</published><updated>2017-09-12T13:52:00+01:00</updated><author><name>Harry</name></author><id>tag:www.obeythetestinggoat.com,2017-09-12:second-edition-is-out-reviews-please.html</id><summary type="html">&lt;figure&gt;
  &lt;img src="/static/images/book_selfie1.jpg" alt="a picture of me attempting a selfie with the book" /&gt;
  &lt;caption&gt;I'm still not great at selfies... Here you get two for the price of one tho.&lt;/caption&gt;
&lt;/figure&gt;

&lt;h3&gt;The second edition is officially out!&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;a href="http://amzn.to/2vl4B6O" target="_top"&gt;
      on Amazon.com
    &lt;/a&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;a href="http://amzn.to/2wJTxOn" target="_top"&gt;
      on Amazon.co.uk
    &lt;/a&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;a href="http://www.ebooks.com/95836888/test-driven-development-with-python/percival-harry-j-w/" target="_top"&gt;
      on ebooks.com (DRM-free)
    &lt;/a&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;a href="https://www.safaribooksonline.com/library/view/test-driven-development-with/9781491958698/" target="_top"&gt;
      on Safari
    &lt;/a&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="http://www.obeythetestinggoat.com/book/acknowledgments.html"&gt;Thanks&lt;/a&gt;
to everyone involved!&lt;/p&gt;
&lt;p&gt;If you've been reading the free online edition and feel like showing your
support, then now's the time!  There's not a huge amount in it for me
financially (I get about a dollar off every sale) but it does add up,
and O'Reilly also deserve your support, they've been great.&lt;/p&gt;
&lt;h3&gt;Reviews, please!&lt;/h3&gt;
&lt;p&gt;More importantly, I really need some reviews for the book, on Amazon and
elsewhere.  They make a real difference to sales, and they really help
other people to make the decision over whether they will find the book
useful.  So if you've enjoyed the book at all, do write a review for me
online.  That would be very, very much appreciated.&lt;/p&gt;
&lt;p&gt;Thanks once again, dear readers, for your kind words and encouragement
throughout the process. I couldn't do it without you.&lt;/p&gt;</summary><category term="Book"></category><category term="second edition"></category><category term="reviews"></category></entry><entry><title>Latest release (the last big one?): Python 3.6, Django 1.11 beta</title><link href="http://www.obeythetestinggoat.com/latest-release-the-last-big-one-python-36-django-111-beta.html" rel="alternate"></link><published>2017-03-13T06:12:00+00:00</published><updated>2017-03-13T06:12:00+00:00</updated><author><name>Harry</name></author><id>tag:www.obeythetestinggoat.com,2017-03-13:latest-release-the-last-big-one-python-36-django-111-beta.html</id><summary type="html">&lt;p&gt;Hi all!  A brief overview of changes in the latest version, with hopefully a few
pointers for people half-way through the book and looking to adapt.&lt;/p&gt;
&lt;h2&gt;Python 3.6 and Django 1.11b: minimal changes, other than f-strings&lt;/h2&gt;
&lt;p&gt;The main feature in Python 3.6 that's made an impact (for the book and,
arguably, elsewhere)
is &lt;a href="https://docs.python.org/3/whatsnew/3.6.html#pep-498-formatted-string-literals"&gt;f-strings&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I have to say when I first heard of them I was pretty skeptical, they sounded
a bit like YOLO-ing &lt;code&gt;**locals()&lt;/code&gt; all over your code, scary, but those concerns
have occurred to the brighter minds than mine that develop Python, and they figured
there was nothing truly to be scared about, so I should relax.&lt;/p&gt;
&lt;p&gt;I'll tell you what really made me relax, which was using them!  I can't count
the number of times I replaced a 3-line &lt;code&gt;string.format()&lt;/code&gt; or an incoprehensible
&lt;code&gt;%s&lt;/code&gt; substitution with an f-string one-liner, and felt my code was nicer and
more readable as a result.&lt;/p&gt;
&lt;p&gt;The move to 3.6 also forced me to upgrade to &lt;a href="https://docs.djangoproject.com/en/dev/releases/1.11/"&gt;Django
1.11&lt;/a&gt; at the same time,
since it's the only version of Django that officially supports Python 3.6,
and that was pretty much entirely painless too.  No major code changes
as a result, and since it's going to be an LTS, I'm hoping I'll be able to
stick with it for a long while over the lifetime of this 2e of the book.&lt;/p&gt;
&lt;h2&gt;Other minor improvements: fabric3&lt;/h2&gt;
&lt;p&gt;While I was at it I decided to upgrade to a Python 3 fork of fabric (thanks
to all the readers who encouraged me to do so), and that saves us from a
pretty awkward hop via subprocess to Python 2.  Here's the old ascii-art diagram,
preserved for posterity:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;## Locally:

MyListsTest
.create_pre_authenticated_session  →   .management.commands.create_session
                                       .create_pre_authenticated_session

## Against staging:

MyListsTest
.create_pre_authenticated_session      .management.commands.create_session
                                       .create_pre_authenticated_session
     ↓
                                                      ↑
server_tools
.create_session_on_server                run manage.py create_session

     ↓                                                ↑
subprocess.check_output    →    fab   →   fabfile.create_session_on_server
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;em&gt;the old, bad way of doing it&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;That's now much neater in the &lt;a href="/book/chapter_server_side_debugging.html"&gt;server-side debugging
chapter&lt;/a&gt;.  Huge thanks to the
&lt;a href="https://github.com/mathiasertl/fabric/"&gt;fabric3 developers&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;What to do if you're half-way through the book&lt;/h2&gt;
&lt;p&gt;I'm always a fan of upgrading!   Because the changes are all quite minor,
you &lt;em&gt;should&lt;/em&gt; be able to just:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Install Python 3.6 (downloadable on windows + macos, can use
  &lt;a href="https://launchpad.net/~fkrull/+archive/ubuntu/deadsnakes"&gt;deadsnakes ppa&lt;/a&gt; on Ubuntu)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;rmvirtualenv superlists&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;re-create the virtualenv with Python 3.6 (see the 
  &lt;a href="/book/pre-requisite-installations.html#_setting_up_your_virtualenv"&gt;pre-reqs chapter&lt;/a&gt;
  for help, particularly on windows)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;pip install django==1.11b1 selenium fabric3&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;What about Selenium 3?&lt;/h2&gt;
&lt;p&gt;If you actually started the book before Jan 30th or so, and you haven't made the upgrade
to Selenium 3 yet, that's a bigger deal, and for that I'd recommend starting again at the
beginning of the book.  Don't worry, you'll zip through the chapters much quicker the
second time, and it should really help bed in the learning.&lt;/p&gt;
&lt;h2&gt;What did I miss?&lt;/h2&gt;
&lt;p&gt;Honestly, it does feel like it was all a bit too easy.  Is there anything I missed?
Any new features from Python 3.6 or Django 1.11 that you think I should have included
in the book?  Let me know!&lt;/p&gt;
&lt;p&gt;Thanks, as ever, for reading.&lt;/p&gt;</summary><category term="Book"></category><category term="second edition"></category><category term="Python 3"></category><category term="Django"></category></entry><entry><title>Upgraded to Selenium 3! (and Geckodriver)</title><link href="http://www.obeythetestinggoat.com/upgraded-to-selenium-3-and-geckodriver.html" rel="alternate"></link><published>2017-02-11T10:12:00+00:00</published><updated>2017-02-11T10:12:00+00:00</updated><author><name>Harry</name></author><id>tag:www.obeythetestinggoat.com,2017-02-11:upgraded-to-selenium-3-and-geckodriver.html</id><summary type="html">&lt;h2&gt;Selenium 3 and Geckodriver&lt;/h2&gt;
&lt;p&gt;Selenium 3 came out earlier this year, and the Mozilla project, those wonderful technohippies,
have been pushing the standard forward (did someone say "bleeding edge"?), and
the lastest versions of Firefox will only work with the new Selenium 3 and its
"geckodriver" client driver.&lt;/p&gt;
&lt;p&gt;The project it still young and has
&lt;a href="https://github.com/mozilla/geckodriver/issues"&gt;plenty&lt;/a&gt; of &lt;a href="https://developer.mozilla.org/en-US/docs/Mozilla/QA/Marionette/WebDriver/status"&gt;minor
bugs&lt;/a&gt; still, but it's
functional enough, and I don't want to have to continue recommending people downgrade to Firefox 45 ESR,
or (Saint RMS forbid!) switch to one of the other evil browsers, compromised as they are by corporate interests.&lt;/p&gt;
&lt;p&gt;So selenium 3 it is!&lt;/p&gt;
&lt;h2&gt;Explicit waits all the way&lt;/h2&gt;
&lt;p&gt;"implicit waits", whereby Selenium automatically tries to wait for things to happen (eg for the page to
load after a click) have always been a bit flakey, and 
&lt;a href="https://groups.google.com/d/msg/selenium-developers/hA_jTx4vrDM/zZOtVX1_jQEJ"&gt;the official line&lt;/a&gt;
is to prefer explicit waits at all times.  And unfortunately with Selenium 3 implicit waits have
become all the more unreliable (or simply &lt;a href="https://github.com/mozilla/geckodriver/issues/308"&gt;not implemented&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;So while in the first edition I was able to avoid the topic of explicit waits all the way until about
chapter 20, in the new one I have to introduce them upfront.  And as a result I've had to insert a
new chapter after chapter 5: &lt;a href="http://www.obeythetestinggoat.com/book/chapter_explicit_waits_1.html"&gt;the first explicit waits chapter&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It did allow me to introduce two wait helpers I'm quite fond of though:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;a generic &lt;a href="https://www.obeythetestinggoat.com/book/chapter_organising_test_files.html#_a_new_functional_test_tool_a_generic_explicit_wait_helper"&gt;wait_for function&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;a generic &lt;a href="http://www.obeythetestinggoat.com/book/chapter_fixtures_and_wait_decorator.html#_our_final_explicit_wait_helper_a_wait_decorator"&gt;wait decorator&lt;/a&gt;  (mmmh, Pythonic!)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Functional programming goodness!&lt;/p&gt;
&lt;p&gt;Overall I think the book is better for it.&lt;/p&gt;
&lt;h2&gt;Lesson: avoid numerical chapter names&lt;/h2&gt;
&lt;p&gt;Inserting a new chapter after chapter 5 proved to be lots of "fun".  I certainly learned the lesson
of avoiding numerical names for my chapter source files!  Up until this point, all the files were
called things like &lt;code&gt;chapter_05.html&lt;/code&gt;.  And of course all the associated source code examples were
in branches named similarly.  And all the book's tests (the meta-tests if you will) have similar
dependencies.  So everything was off by one, and I've spent the last few weeks
&lt;a href="https://github.com/hjwp/Book-TDD-Web-Dev-Python/commits/rename-all-chapters"&gt;shaving that yak&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Also: computers.&lt;/h2&gt;
&lt;p&gt;I've enabled comments on each chapter using Disqus. By default it uses URLs as
the identifier for my threads. So the recent renumbering meant about half the
comments ended up on the wrong pages. Thankfully Disqus have a tool to deal with
this called the "URL mapper", and "hooray", I thought, problem solved, and so I
submitted a mapping a bit like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;page7 -&amp;gt; page8
page8 -&amp;gt; page9
page9 -&amp;gt; page10
etc
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Turns out that was a disaster. The disqus migration tool took me at my word I
guess, and went down the list in order, moving all the comments from page 7
onto page 8 (can you guess what happens next?) Then it took all the comments on
page 8 (which now included the ones from page 7), and moved them onto page 9...
and the end result is that almost all the pages have no comments, except for
the last page, which has ended up with all the 600 comments from all the other
pages glommed together in a massive mess. oh no. computers eh?&lt;/p&gt;
&lt;p&gt;Pending help from disqus (which 
&lt;a href="https://disqus.com/home/discussion/channel-discussdisqus/admin_undo_url_mapper_or_manually_reassign_comments_to_threads/"&gt;doesn't seem to be forthcoming&lt;/a&gt;) it'll have
to be a fresh start on comments then!&lt;/p&gt;
&lt;h2&gt;As always, your feedback is enthusiastically solicited&lt;/h2&gt;
&lt;p&gt;Follow the links above for some of the content that's specifically new to
Selenium 3, and let me know what you think!&lt;/p&gt;</summary><category term="Book"></category><category term="second edition"></category><category term="Selenium"></category></entry><entry><title>Second Edition update: Virtualenvs, Django 1.10, REST APIs, cleaner FTs...</title><link href="http://www.obeythetestinggoat.com/second-edition-update-virtualenvs-django-110-rest-apis-cleaner-fts.html" rel="alternate"></link><published>2016-12-06T17:12:00+00:00</published><updated>2016-12-06T17:12:00+00:00</updated><author><name>Harry</name></author><id>tag:www.obeythetestinggoat.com,2016-12-06:second-edition-update-virtualenvs-django-110-rest-apis-cleaner-fts.html</id><summary type="html">&lt;p&gt;A brief update on my progress for the second edition. &lt;/p&gt;
&lt;p&gt;&lt;img src="/static/images/second_edition_plan.png" alt="screenshot of book project plan, almost done" width="80%" /&gt;
&lt;em&gt;Getting there!&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Virtualenvs all the way down.&lt;/h2&gt;
&lt;p&gt;In the first edition, I made the judgement call that telling people to use virtualenvs
at the very beginning of the book would be too confusing for beginners.  I've decided
to revisit that decision, since virtualenvs are more and more &lt;em&gt;de rigueur&lt;/em&gt; these days.
I mean, if the &lt;a href="https://tutorial.djangogirls.org/"&gt;djangogirls tutorial&lt;/a&gt; is recommending
one, given that it's the most beginner-friendly tutorial on Earth, then it really must
be a good idea.  So there's new instructions in the 
&lt;a href="http://www.obeythetestinggoat.com/book/pre-requisite-installations.html#_setting_up_your_virtualenv"&gt;pre-requisite installations chapter&lt;/a&gt;.  Let me know if you think they could be clearer.&lt;/p&gt;
&lt;h2&gt;Django 1.10&lt;/h2&gt;
&lt;p&gt;Django 1.10 doesn't introduce that many new features over 1.8, but upgrading was still
pretty fiddly.  Thank goodness for my
&lt;a href="https://github.com/hjwp/Book-TDD-Web-Dev-Python/tree/master/tests"&gt;extensive tests&lt;/a&gt;
(tests for the tests in the book about testing, yes. because of course.)  The main changes
you'll likely to notice is in
&lt;a href="http://www.obeythetestinggoat.com/book/chapter_04.html#_the_don_t_test_constants_rule_and_templates_to_the_rescue"&gt;Chapter 4&lt;/a&gt;
where I introduce the Django Test Client, much earlier than I used to (which,
through a long chain of causes, is actually because of a 
&lt;a href="https://code.djangoproject.com/ticket/20869"&gt;change to the way csrf tokens are generated&lt;/a&gt;).
Other than that, Django 1.10 was pretty much a drop-in replacement.  The main thing
I'm preparing for really is the upgrade to 1.11LTS early next year.&lt;/p&gt;
&lt;h2&gt;REST APIs&lt;/h2&gt;
&lt;p&gt;I was thinking of having a couple of in-line chapters on building a REST API,
but for now I've decided to have them as appendices.  It starts with 
&lt;a href="http://www.obeythetestinggoat.com/book/appendix_VI_rest_api.html"&gt;how to roll your own&lt;/a&gt;,
 including an example of 
&lt;a href="http://www.obeythetestinggoat.com/book/appendix_VI_rest_api.html#_testing_the_client_side_with_sinon_js"&gt;how to test client-side ajax javascript with sinon&lt;/a&gt;,
and then there's a 
&lt;a href="http://www.obeythetestinggoat.com/book/appendix_VII_DjangoRestFramework.html"&gt;second appendix on Django Rest Framework&lt;/a&gt;.
These are both very much just skeleton outlines at the moment, but, still,
feedback and suggestions appreciated.&lt;/p&gt;
&lt;h2&gt;A cleaner flow for Chapter 6&lt;/h2&gt;
&lt;p&gt;Chapter 6 is all about rewriting an app that &lt;em&gt;almost&lt;/em&gt; works, to be one
that actually works, but trying to work incrementally all along, and using
the FTs to tell us when we make progress, and warn us if we introduce 
regressions.  I used to have just the one FT, and track progress/regressions
by "what line number is the FT failing at?  is it higher or lower than before?".
Instead I've split out 
&lt;a href="http://www.obeythetestinggoat.com/book/chapter_06.html#_ensuring_we_have_a_regression_test"&gt;one FT that tests that the existing behaviour still works&lt;/a&gt;,
and one FT for the new behaviour, and that's much neater I think.&lt;/p&gt;
&lt;h2&gt;Next: geckodriver and Selenium 3 (uh-oh!)&lt;/h2&gt;
&lt;p&gt;There are plenty more little tweaks and nice-to-have additions I can think
of (React? Docker? Oh yeah, I got your trendy topics covered), but the main
task that's really outstanding is upgrading to Selenium 3 and geckodriver.
And the reason that's scary is because the
&lt;a href="https://lists.w3.org/Archives/Public/public-browser-tools-testing/2016OctDec/0033.html"&gt;current status of implicit waits is up for debate&lt;/a&gt;,
and I rely on implicit waits a lot.
Introducing explicit waits earlier might be a good thing (they're currently
only mentioned in Chapter 20), but it would definitely add to the learning
curve in the early chapters (I think they'd have to go in chapter 4 or 5,
which feels very early indeed).  So I'm kinda in denial about this at the
moment, hoping that maybe Mozilla will reintroduce the old behaviour, or
maybe I'll build some magical wrapper around selenium that just does implicit
waits for you  (maybe using my
&lt;a href="http://www.obeythetestinggoat.com/how-to-get-selenium-to-wait-for-page-load-after-a-click.html"&gt;stale element check trick&lt;/a&gt;)
(in my copious spare time), or maybe switch to chromedriver, or I don't 
know I don't want to think about it.  Suggestions, words of encouragement,
moral support all welcome here.&lt;/p&gt;
&lt;p&gt;In the meantime, I hope you enjoy the new stuff.  Keep in touch!&lt;/p&gt;</summary><category term="Book"></category><category term="second edition"></category><category term="Django"></category><category term="REST"></category></entry><entry><title>Plans for the second edition</title><link href="http://www.obeythetestinggoat.com/plans-for-the-second-edition.html" rel="alternate"></link><published>2016-09-25T18:52:00+01:00</published><updated>2016-09-25T18:52:00+01:00</updated><author><name>Harry</name></author><id>tag:www.obeythetestinggoat.com,2016-09-25:plans-for-the-second-edition.html</id><summary type="html">&lt;p&gt;The second edition was mostly prompted by the &lt;a href="https://mail.mozilla.org/pipermail/persona-notices/2016/000005.html"&gt;announcement by
Mozilla&lt;/a&gt;
that they were shutting down Persona in November 2016.  Given
that it would affect almost all the chapters from 15 thru to 21,
it seemed a good excuse to do a full second edition rather than
just an update.&lt;/p&gt;
&lt;p&gt;Here, in brief, is an outline of the plan:&lt;/p&gt;
&lt;h2&gt;Chapter rewrites:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Rewrite chapters 15 + 16, replace persona with passwordless auth:
  &lt;a href="http://www.obeythetestinggoat.com/book/chapter_15.html"&gt;first draft done&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Update chapters 17+ for persona changes: in progress&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Update JavaScript chapter for new version of QUnit: &lt;a href="http://www.obeythetestinggoat.com/book/chapter_14.html"&gt;done&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Update deployment chapters to use Systemd instead of Upstart: started  but only in &lt;a href="http://www.obeythetestinggoat.com/book/appendix_III_provisioning_with_ansible.html#_configuring_gunicorn_and_using_handlers_to_restart_services"&gt;ansible appendix&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Two new chapters on REST APIs and Ajax: 
  &lt;a href="https://github.com/hjwp/book-example/commits/rest-api-spike"&gt;code spiked&lt;/a&gt;, but
  chapters not yet written&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Minor updates + changes:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Switch to using a virtualenv from the very beginning&lt;/li&gt;
&lt;li&gt;Upgrade to latest Django (1.10?)&lt;/li&gt;
&lt;li&gt;Use less HTML ids and more classes&lt;/li&gt;
&lt;li&gt;Use more early returns in FTs when refactoring partially finished user stories.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That's it, in very brief.  You can read more
&lt;a href="https://groups.google.com/forum/#!topic/obey-the-testing-goat-book/fCENUr_NawM"&gt;on the google group&lt;/a&gt;,
and feel free to join in the discussion there too, or here.  Let me know what
you think!&lt;/p&gt;</summary><category term="Book"></category><category term="second edition"></category><category term="Persona"></category></entry><entry><title>EuroPython 2016 call for beginners' day coaches.</title><link href="http://www.obeythetestinggoat.com/europython-2016-call-for-beginners-day-coaches.html" rel="alternate"></link><published>2016-07-10T11:33:00+01:00</published><updated>2016-07-10T11:33:00+01:00</updated><author><name>Harry</name></author><id>tag:www.obeythetestinggoat.com,2016-07-10:europython-2016-call-for-beginners-day-coaches.html</id><summary type="html">&lt;blockquote&gt;
&lt;p&gt;Coming to EuroPython? Will you be there by the Sunday before it all starts?
(the 17th) Want to help some beginners to get to grips with Python, and
get the most out of the conference? I need your help!  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It's always surprising how many beginners come to Python conferences.  The
&lt;a href="https://ep2016.europython.eu/en/events/beginners-day/"&gt;beginners' day&lt;/a&gt; is
a full-day event aimed at helping them to get the best out of the conference
-- getting them up to speed on the basics of Python, but also giving them
a bit of a tour around the Python ecosystem, Python in-jokes and jargon,
and what the conference is all about.&lt;/p&gt;
&lt;p&gt;Here's an example of a happy beginner from last year:&lt;/p&gt;
&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/l4t2iMlKN9Q" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;

&lt;p&gt;The day is mainly structured around self-directed learning, where attendees
follow one of several selected tutorials, at their own speed.  Most imporantly
of all, they sit at tables with coaches -- more experienced Python programmers
ready to help them when they get stuck, explain things when they get confusing,
and help them find solutions to problems.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(if you've ever attended a Djangogirls workshop, you'll see where I've
shamelessly stolen the idea from ;)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I already have several coaches signed up, but it'd be great to have a few
more. You don't have to be a Python expert, just someone that remembers what it
was like to be a beginner, someone patient and kind and keen to help. Here's
&lt;a href="http://coach.djangogirls.org/tips/"&gt;more on how to be a good coach&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The workshop starts at 10AM on the Sunday before the conference, the 17th.&lt;/p&gt;
&lt;p&gt;Helping beginners is a great feeling, I hope you'll join me!  Emails to 
&lt;a href="mailto:obeythetestinggoat@gmail.com"&gt;obeythetestinggoat@gmail.com&lt;/a&gt; please :-)&lt;/p&gt;</summary><category term="Workshop"></category><category term="EuroPython"></category><category term="Beginners"></category></entry><entry><title>Coaches needed for Python Bootcamp @ Pycon US in Portland next week.</title><link href="http://www.obeythetestinggoat.com/coaches-needed-for-python-bootcamp-pycon-us-in-portland-next-week.html" rel="alternate"></link><published>2016-05-18T14:33:00+01:00</published><updated>2016-05-18T14:33:00+01:00</updated><author><name>Harry</name></author><id>tag:www.obeythetestinggoat.com,2016-05-18:coaches-needed-for-python-bootcamp-pycon-us-in-portland-next-week.html</id><summary type="html">&lt;blockquote&gt;
&lt;p&gt;Coming to Pycon? Want to help some beginners to get to grips with Python, and
get the most out of the conference? I need your help!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Having observed the surprising number of beginners to be found at Python
conferences, I organised a "beginners' day" at last year's EuroPython in
Bilbao.  We had about 30 beginners attend (from a crowd of 1200 at the
conference) -- quite a few science types, some people who already knew another
language but wanted to learn Python, and a few total beginners. Everyone seemed
to leave with lots of nice things to say about it.&lt;/p&gt;
&lt;p&gt;Here's a couple of clips:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=l4t2iMlKN9Q"&gt;A happy beginner&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=WI-BFxeumv8"&gt;The oath of the mentor&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I'm running something similar at Pycon this year, on the first day of the tutorials:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://us.pycon.org/2016/schedule/presentation/2258/"&gt;Python Bootcamp at Pycon USA 2016, May 28th&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The basic idea is shamelessly stolen from the awesome
&lt;a href="https://djangogirls.org/"&gt;DjangoGirls&lt;/a&gt;, and is mainly
structured around:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;self-directed learning&lt;/strong&gt; based on a selection of Python &lt;strong&gt;tutorials&lt;/strong&gt; (one
  for total beginners, one for sciencey types, one for web developers, one for
  programmers who already know another language, and so on)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;coached&lt;/strong&gt; by friendly Python developers sitting near each attendee&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;supplemented by &lt;strong&gt;a few short lectures&lt;/strong&gt; on the Python "ecosystem" (what is
  the cheese shop / the bdfl / a pep / pip / requests / IPython notebook / etc
  etc)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There's more info in the
&lt;a href="https://github.com/hjwp/python-conference-beginners-day"&gt;repo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I need more coaches!  I would love to get to a 3-to-1 ratio of attendees to
mentors.  You don't have to be an expert Python developer, you just have to be
a friendly person, that remembers how tough it can be as a beginner, and that
wants to help.  I expect quite a few "data science" people in particular, so
if you have skills in that area (even if you've only just learned them yourself),
I want you!&lt;/p&gt;
&lt;p&gt;Check out the &lt;a href="http://coach.djangogirls.org/"&gt;DjangoGirls coach manual&lt;/a&gt; for
a flavour of the kind of attitude I'm trying to cultivate.  And don't forget
you'll have to swear the "Oath of the Mentor", as per the link above.&lt;/p&gt;
&lt;p&gt;Helping beginners is a great feeling, I hope you'll join me!  Emails to 
&lt;a href="mailto:obeythetestinggoat@gmail.com"&gt;obeythetestinggoat@gmail.com&lt;/a&gt; please :)&lt;/p&gt;
&lt;p&gt;&lt;em&gt;PS: This is not a free event for attendees, but I am donating my entire fee
for it back to the PSF.  Minus, perhaps, a small budget set aside for
"thankyou" drinks and cakes for coaches :)&lt;/em&gt;&lt;/p&gt;</summary><category term="Workshop"></category><category term="Pycon"></category><category term="Beginners"></category></entry><entry><title>Your feedback required re: a new edition</title><link href="http://www.obeythetestinggoat.com/your-feedback-required-re-a-new-edition.html" rel="alternate"></link><published>2016-04-18T10:22:00+01:00</published><updated>2016-04-18T10:22:00+01:00</updated><author><name>Harry</name></author><id>tag:www.obeythetestinggoat.com,2016-04-18:your-feedback-required-re-a-new-edition.html</id><summary type="html">&lt;p&gt;&lt;a href="https://mail.mozilla.org/pipermail/persona-notices/2016/000005.html"&gt;Mozilla has announced&lt;/a&gt;
that they are retiring the Persona project, which I use in the book from
chapter 15 onwards.  This is sad news, because it was a great project, but also
because it's going to break those chapters!  So O'Reilly have suggested I write
up a new edition of the book, and I'd like to get your suggestions and
feedback.&lt;/p&gt;
&lt;p&gt;It's not meant to be a wholesale rewrite, mainly just find a replacement for
Persona, and then address a few improvements if I have time.   So my questions
to you are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What would you replace Persona with?&lt;/li&gt;
&lt;li&gt;What other changes or improvements would you like to see in the book?&lt;/li&gt;
&lt;li&gt;Are there any totally new topics you'd love to see covered? (and I'm not making any promises here!)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I've kicked off a discussion on the book's mailing list, and there's talk of JavaScript MVC frameworks, a REST API, docker, and much else.  &lt;/p&gt;
&lt;p&gt;&lt;a href="https://groups.google.com/forum/#!topic/obey-the-testing-goat-book/fCENUr_NawM"&gt;Please join the discussion!&lt;/a&gt;&lt;/p&gt;</summary><category term="Book"></category><category term="Authentication"></category><category term="Javascript MVCs"></category><category term="new edition"></category></entry><entry><title>Intermediate TDD workshops in London and Portland</title><link href="http://www.obeythetestinggoat.com/intermediate-tdd-workshops-in-london-and-portland.html" rel="alternate"></link><published>2016-03-31T13:22:00+01:00</published><updated>2016-03-31T13:22:00+01:00</updated><author><name>Harry</name></author><id>tag:www.obeythetestinggoat.com,2016-03-31:intermediate-tdd-workshops-in-london-and-portland.html</id><summary type="html">&lt;p&gt;After many successful years of running my beginners' TDD/Django tutorial, I
thought it might be time to have a crack at some more intermediate-level
topics, so I'm announcing a couple of workshops.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;the first is &lt;a href="https://skillsmatter.com/meetups/7997-in-the-brain-of-harry-percival"&gt;in London, on Monday the 4th of April, at Skillsmatter&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;the second is in &lt;a href="https://us.pycon.org/2016/schedule/presentation/1713/"&gt;Portland, Oregon, on Sunday the 29th of May, at Pycon&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;The aim will be to cover some intermediate-level topics.  It assumes you've already started doing a bit of TDD, you've wrapped your head around unit testing, and some basic mocking, but you're starting to ask questions like: What order should I write tests in?  When should I mock, and when should I not?  What are the pros and cons of isolated unit tests vs integration tests?  What do people mean when they say "let the tests drive the design"?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I'm not claiming that I'll come up with perfect answer to these questions, or solve all your problems, but we will go through a worked example that will help to illustrate some of the tradeoffs, and hopefully help you to think about how to apply this for your own projects.   It's based on chapters 17 and 18 in &lt;a href="/pages/book.html"&gt;my book&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;That London one is next Monday, so book up quick!&lt;/p&gt;
&lt;p&gt;And come prepared!  You'll need:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;git clone https://github.com/hjwp/book-example/ tdd-workshop
&lt;span class="nb"&gt;cd&lt;/span&gt; tdd-workshop
git fetch --tags
git checkout chapter_17
mkvirtualenv --python&lt;span class="o"&gt;=&lt;/span&gt;python3 tdd-workshop  &lt;span class="c1"&gt;# or however you like to create virtualenvs&lt;/span&gt;
pip install -r requirements.txt
pip install selenium
python manage.py &lt;span class="nb"&gt;test&lt;/span&gt;  &lt;span class="c1"&gt;# these should all pass&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Ask me if you have any problems!  &lt;a href="mailto:obeythetestinggoat@gmail.com"&gt;obeythetestinggoat@gmail.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;[update monday PM]&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;If the functional tests are being strange, try switching from firefox to chrome.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Download chromedriver from here: https://sites.google.com/a/chromium.org/chromedriver/downloads&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Extract it and add it to your path&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Switch &lt;code&gt;webdriver.Firefox()&lt;/code&gt; to &lt;code&gt;webdriver.Chrome()&lt;/code&gt; in &lt;em&gt;functional_tests/base.py&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</summary><category term="Workshop"></category><category term="Outside-In"></category><category term="Mocks"></category></entry><entry><title>Testing, async, asyncio, and performance</title><link href="http://www.obeythetestinggoat.com/testing-async-asyncio-and-performance.html" rel="alternate"></link><published>2015-12-27T13:29:00+00:00</published><updated>2015-12-27T13:29:00+00:00</updated><author><name>Harry</name></author><id>tag:www.obeythetestinggoat.com,2015-12-27:testing-async-asyncio-and-performance.html</id><summary type="html">&lt;p&gt;I recently did some experimenting with &lt;code&gt;asyncio&lt;/code&gt;, and wanted to report back on
how I got on with writing tests for it.  While I was at it I was also able to
compare its performance with a couple of other approaches to mutlitasking in
Python, namely threads and gevent, so I'll report on that here too.  (tl;dr:
it's much of a muchness).&lt;/p&gt;
&lt;h3&gt;"hobbling" naughty user processes&lt;/h3&gt;
&lt;p&gt;At &lt;a href="https://www.pythonanywhere.com/"&gt;PythonAnywhere&lt;/a&gt; we have a "tarpit" where we
put users who exceed their usage limits.  Their processes still run, but
slower.  We use cgroups for this, and it works pretty well, but I was in a mean
mood and I wanted to see whether I could make our tarpit even more mean --
essentially "hobbling" naughty users's processes using OS stop and restart
signals.&lt;/p&gt;
&lt;p&gt;In synchronous/pseudo code, something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;naughty_processes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;find_new_naughty_processes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;naughty_processes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;hobble_process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# parallelised somehow&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hobble_process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SIGSTOP&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.25&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SIGCONT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Every 10 seconds, go fetch a list of "naughty" processes, and then "hobble"
each one of them, by using OS signals to stop and start the process at short
intervals.  The naughty program still runs, but its execution is suspended for
95% of the time.&lt;/p&gt;
&lt;h3&gt;A first cut using asyncio&lt;/h3&gt;
&lt;p&gt;The only question was: how to do the "parallelise somehow" part.  Asyncio is
the hot new thing in the world of Python async stuff, and this seemed like a
good potential candidate -- I have a fairly simple algorithms, and there are
lots of places where I use "time.sleep", which are good places to give back
control to some sort of event loop or task manager.&lt;/p&gt;
&lt;p&gt;And sure enough, my first cut of the same code with asyncio was pleasingly
similar to the normal procedural code -- I just add a few "yield froms" to
signify where each function can yield control back to the event loop, ready to
be woken up again when there's something for it to do:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;loop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_event_loop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;loop&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hobble_processes_forever&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;loop&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run_forever&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="nd"&gt;@asyncio.coroutine&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hobble_processes_forever&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;already_hobbled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;yield from&lt;/span&gt; &lt;span class="n"&gt;hobble_current_processes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;already_hobbled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;yield from&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@asyncio.coroutine&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hobble_current_processes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;already_hobbled&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;pids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield from&lt;/span&gt; &lt;span class="n"&gt;get_naughty_pids&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;pids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;already_hobbled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="n"&gt;already_hobbled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_event_loop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;hobble_process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@asyncio.coroutine&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hobble_process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SIGSTOP&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;yield from&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SIGCONT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;yield from&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;feel free to skip this next section if you already know asyncio&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;What is a coroutine, I hear you ask?  Or, at least, I hear some of you ask.
I'm relying on the ones who don't ask to improve the amateurish definition that
follows. Ahem.  A coroutine is a function that defines some points at which
it's happy to suspend and resume execution, or wait until some data or device
becomes available. &lt;a href="https://en.wikipedia.org/wiki/Coroutine"&gt;wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In asyncio, those places are marked by the &lt;code&gt;yield from&lt;/code&gt; keywords, where the
function says: I know this next thing will take some time, I'm happy to wait
and let the rest of the program (as controlled by the event loop) get on with
something else.&lt;/p&gt;
&lt;p&gt;As I learned while trying to build this thing, &lt;code&gt;yield from&lt;/code&gt; on its own won't
make your code asynchronous (check out &lt;a href="https://gist.github.com/hjwp/727c932ce3e20c6367e5"&gt;this
gist&lt;/a&gt; for an illustration).
You also need a special way of invoking functions that you want to start off
asynchronously, and that's the purpose of &lt;code&gt;create_task&lt;/code&gt;.  &lt;code&gt;create_task&lt;/code&gt; tells
the event loop to start a function "in the background".&lt;/p&gt;
&lt;h3&gt;testing async code with a functional test&lt;/h3&gt;
&lt;p&gt;Once I'd more or less wrapped my head around that, and built a prototype that
works, I started to think about testing. Or, how to turn my manual testing into
automated testing&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Some wag recently said "When people tell me they don't do TDD, I usually see
them driving development with a bunch of manual tests which they're going to
throw away, instead of automating them.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It felt like some "real" tests were in order, tests that would actually start
some real processes and see if they really did get slowed down -- an end-to-end
test, if you will -- so that's what I went for.  Here was my first cut:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_hobbled_process_is_slow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tarpit_pids_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start_hobbler_in_subprocess&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;timer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;; &amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;import time&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;time.sleep(0.4)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# give hobbler a chance to spot us&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;start = time.time()&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;list(range(int(1e6)))&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# do some work&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;print(time.time() - start)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;normal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;check_output&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;python&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;-c&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="n"&gt;add_self_to_tarpit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;import os;&amp;quot;&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot; open({pidsfile}, &amp;#39;w&amp;#39;).write(str(os.getpid()));&amp;quot;&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pidsfile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tarpit_pids_file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;slow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;check_output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;python&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;-c&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;add_self_to_tarpit&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot; &amp;quot;&lt;/span&gt;  &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;normal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;normal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;slow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;normal&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;slow&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;normal&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;slow&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;normal&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;slow&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The test depends on two fixtures, one to create a file containing process ids
(pids) that we want to hobble, and one to actually launch the hobbler.py process&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nd"&gt;@pytest.yield_fixture&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;tarpit_pids_file&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;tempfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NamedTemporaryFile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="nd"&gt;@pytest.yield_fixture&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start_hobbler_in_subprocess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tarpit_pids_file&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;process&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Popen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;python3&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;hobbler.py&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tarpit_pids_file&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PIPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stdin&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PIPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;STDOUT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;universal_newlines&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;first_line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readline&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Traceback&amp;#39;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;first_line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;

    &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kill&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;full hobbler process output:&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;If you're not familiar with py.test fixtures, they're like things you might put
in a unittest setUp / tearDown; special functions that get called with each test
that names them as an argument.  The &lt;code&gt;yield&lt;/code&gt; pattern I'm using here allows me to
inject a resource into the test.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Running the code under test as a totally separate Python process has two
benefits -- first, it lets me test the program as it will actually be used,
and secondly, it neatly sidesteps one of the difficulties of testing async code,
which is how to deal with the event loop itself, which has to be launched as a
blocking call..&lt;/p&gt;
&lt;p&gt;From that point onwards, I found it was relatively easy to use similar tests
to drive my development, alongside a few manual checks. Here's my final list
of tests:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_tarpit_process_is_slow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fake_tarpit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hobbler_process&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_spots_process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fake_tarpit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hobbler_process&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_spots_multiple_processes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fake_tarpit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hobbler_process&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_doesnt_hobble_any_old_process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fake_tarpit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hobbler_process&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_stops_hobbling_dead_processes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fake_tarpit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hobbler_process&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_hobbles_children&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fake_tarpit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hobbler_process&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_lots_of_processes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fake_tarpit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hobbler_process&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_get_top_level_processes_returns_list_of_parents_and_with_chidren&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;You can explore these, and the implementation, in the 
&lt;a href="https://github.com/hjwp/process-hobbler-experiment"&gt;repo on GitHub&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Performance comparison:  aysncio vs gevent vs threads&lt;/h3&gt;
&lt;p&gt;Maybe the most interesting test was the "lots of processes" test, which is a
performance test -- since the hobbler is meant to reduce the load on our
servers, by reducing the resource usage of user processes, it's important that
the hobbler itself shouldn't chew up all the CPU!  So I wanted to see how it
performs with lots of processes to hobble:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_lots_of_processes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fake_tarpit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hobbler_process&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;start_times&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hobbler_process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cpu_times&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;start times&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start_times&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;procs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Popen&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;sleep&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;100&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;universal_newlines&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;_add_to_tarpit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fake_tarpit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;procs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# time for 3 iterations&lt;/span&gt;

    &lt;span class="n"&gt;end_times&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hobbler_process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cpu_times&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;end times&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end_times&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;end_times&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;start_times&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;end_times&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;start_times&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;

    &lt;span class="n"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hobbler_process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cpu_percent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;interval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# warm-up&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hobbler_process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cpu_percent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;interval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;All that boils down to starting 100 processes, telling the hobbler to hobble
all of them, and then measuring the CPU usage of the hobbler -- I wanted it to
be less than 10% of CPU. Unfortunately, it was far from that, taking up over
100% CPU in my first test.&lt;/p&gt;
&lt;p&gt;So I thought I'd compare asyncio with a couple of other popular Python
multitasking solutions: 
&lt;a href="http://www.gevent.org/"&gt;gevent&lt;/a&gt; and plain old
&lt;a href="https://docs.python.org/3/library/threading.html"&gt;threads&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Switching from asyncio to gevent was actually very simple -- their programming patterns are
quite similar -- a matter of changing a few &lt;code&gt;aysncio.sleep&lt;/code&gt;s to &lt;code&gt;gevent.sleep&lt;/code&gt;s,
and &lt;code&gt;loop.create_task&lt;/code&gt;s to &lt;code&gt;gevent.spawn&lt;/code&gt;s:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gd"&gt;-            yield from asyncio.sleep(0.01)&lt;/span&gt;
&lt;span class="gi"&gt;+            gevent.sleep(0.01)&lt;/span&gt;

&lt;span class="gd"&gt;-        loop.create_task(&lt;/span&gt;
&lt;span class="gd"&gt;-            hobble_process_tree(parent)&lt;/span&gt;
&lt;span class="gd"&gt;-        )&lt;/span&gt;
&lt;span class="gi"&gt;+        gevent.spawn(hobble_process_tree, parent)&lt;/span&gt;

&lt;span class="gd"&gt;-        yield from hobble_current_processes(loop, already_hobbled, cgroup_dir)&lt;/span&gt;
&lt;span class="gi"&gt;+        gevent.spawn(hobble_current_processes, already_hobbled, cgroup_dir)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Check out the full diff here, if you're curious:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/hjwp/process-hobbler-experiment/commit/gevent"&gt;https://github.com/hjwp/process-hobbler-experiment/commit/gevent&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It turns out that didn't buy me any real performance improvement though.  So I
thought I'd try good ol' fashioned threads&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gd"&gt;-            yield from asyncio.sleep(0.01)&lt;/span&gt;
&lt;span class="gi"&gt;+            time.sleep(0.01)&lt;/span&gt;

&lt;span class="gd"&gt;-        loop.create_task(&lt;/span&gt;
&lt;span class="gd"&gt;-            hobble_process_tree(parent)&lt;/span&gt;
&lt;span class="gd"&gt;-        )&lt;/span&gt;
&lt;span class="gi"&gt;+        threading.Thread(&lt;/span&gt;
&lt;span class="gi"&gt;+            target=lambda: hobble_process_tree(parent)&lt;/span&gt;
&lt;span class="gi"&gt;+        ).start() &lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Again, not a massive change to the programming model -- a &lt;code&gt;loop.create_task&lt;/code&gt; becomes
a &lt;code&gt;Thread().start()&lt;/code&gt;, and the &lt;code&gt;asyncio.sleep()&lt;/code&gt; can just be a normal &lt;code&gt;time.sleep()&lt;/code&gt;,
since we're off the main thread.&lt;/p&gt;
&lt;p&gt;Check out the full diff here if you're curious&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/hjwp/process-hobbler-experiment/commit/threads"&gt;https://github.com/hjwp/process-hobbler-experiment/commit/threads&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But, performance-wise, it was no better.  Worse, in fact!&lt;/p&gt;
&lt;h3&gt;Final test results:  asyncio vs gevent vs threads&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;WARNING: NO SCIENCE HERE!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is the results of just a single run on a single machine, not to be taken
as a general indication of the true, intrinsic performance characteristics of
any of these libraries, etc etc.&lt;/p&gt;
&lt;table&gt;
&lt;tr&gt;&lt;th&gt; Library &lt;/th&gt;    &lt;th&gt; CPU usage  &lt;/th&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt; asyncio: &lt;/td&gt;   &lt;td&gt; 85.4  &lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt; gevent:  &lt;/td&gt;   &lt;td&gt; 100.4 &lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt; threading: &lt;/td&gt; &lt;td&gt; 172.8 &lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;

&lt;p&gt;Feel free to try and replicate these tests yourself, using the code &lt;a href="https://github.com/hjwp/process-hobbler-experiment/commits/master"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Call to action (1): can this be made more efficient?&lt;/h3&gt;
&lt;p&gt;I'm inclined to think that this whole process hobbler was just a bad idea, but
maybe you know more about multitasking stuff, and you can see some obvious
improvements for my code, or have some suggestions for different approaches?
Any thoughts on the theoretical reasons for why asyncio should seem so much 
quicker than threads in this instance? Answers on a postcard please...&lt;/p&gt;
&lt;h3&gt;Call to action (2): better ways of testing async code and/or process behaviour?&lt;/h3&gt;
&lt;p&gt;I use functional tests here for a few reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The code under test crosses a lot of system boundaries -- it affects other
  processes, it relies on operating system signals, and it reads from a file.
  At least one end-to-end / integrationey test felt necessary.  (this feels
  like a good reason)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Once I'd started, it was easier to just continue in that model, rather than
  figure out the subtleties of factoring out my code for testability, dealing
  with testing the event loop, and mocking all the different layers involved.
  This, maybe, is a bad reason.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The price I paid was a fairly slow test suite -- the basic test of process hobbling
takes 8 or 9 seconds, and any of the other tests still take on the order of a second,
since they involve writing to a temp file, starting a hobbler process and a test process,
waiting for the hobbler to do something, and cleaning up.  The performance test takes a
a good 30 seconds on its own.&lt;/p&gt;
&lt;p&gt;The tests are also a little more flakey than I'd like -- the exact amount that
a process gets hobbled is subject to quite a lot of random noise, so just
checking that it's "between 10 and 100 times slower" is a bit more vague than
you might want, and it took me a while to set up the test in such a way that it
reliably passed and failed as appropriate.&lt;/p&gt;
&lt;p&gt;On the other hand, of course, I get the benefit you always get from a functional test,
which is that I'm sure my system really does work.&lt;/p&gt;
&lt;p&gt;So, I'd be interested -- would you write this test suite differently?  Can you see any
candidates for more "unitey" tests?&lt;/p&gt;
&lt;p&gt;You'll see I already made a start on a slightly more isolated test that aims at just
the &lt;code&gt;get_top_level_processes&lt;/code&gt; function.  The other main candidate that seems like it
could do with a better, more granular test is &lt;code&gt;hobble_process_tree&lt;/code&gt; function.
Specifically, it's important that the process tree should be hobbled in the
correct order, starting with the top level, then down to children, then
children's children, and so on, and then re-started in the reverse order,
starting from the bottom-level processes and going back up to the top.
(otherwise, if you're hobbling a users' interactive console session, which has
a nested tree of, say, bash and ipython, the user sees really weird things if
you pause the ipython before the bash.)&lt;/p&gt;
&lt;p&gt;That's not well tested at the moment.  Can you think of a way of doing so which can handle
the interspered asyncio &lt;code&gt;yield from&lt;/code&gt;s, and that still feels like testing
behaviour, not implementation?&lt;/p&gt;</summary><category term="Async"></category><category term="asyncio"></category><category term="peformance"></category></entry><entry><title>Better tests for Redis integrations with redislite</title><link href="http://www.obeythetestinggoat.com/better-tests-for-redis-integrations-with-redislite.html" rel="alternate"></link><published>2015-12-01T08:29:00+00:00</published><updated>2015-12-01T08:29:00+00:00</updated><author><name>Harry</name></author><id>tag:www.obeythetestinggoat.com,2015-12-01:better-tests-for-redis-integrations-with-redislite.html</id><summary type="html">&lt;p&gt;A colleague and I were staring at some ugly, mocky tests for our redis integration the other day, when I remembered someone at Pycon last year showing me a cool library called &lt;a href="https://github.com/yahoo/redislite"&gt;redislite&lt;/a&gt; -- basically, a lightweight, self-contained, pip installable version of redis, that can be installed almost anywhere and run totally separately from the system redis.  (That was &lt;a href="https://twitter.com/dwighthubbard"&gt;Dwight&lt;/a&gt;, who I now realise is the main author of redislite.  Yay Pycon.)&lt;/p&gt;
&lt;p&gt;He'd said "it's great for testing", so we thought we'd give it a go.&lt;/p&gt;
&lt;p&gt;&lt;img src="/static/images/pip_install_redislite.png" alt="console output from pip installing redislite"&gt;&lt;/img&gt;
&lt;caption&gt;&lt;i&gt;pip installs and compiles in 20 seconds&lt;/i&gt;&lt;/caption&gt;&lt;/p&gt;
&lt;p&gt;Here's the sample usage from the docs:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;redislite&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redislite&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StrictRedis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;foo&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;bar&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;foo&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;bar&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Couldn't be simpler!  You can initiate a lightweight version of redis just like that, have one for each test even, and throw it away at the end, without having to worry about ports, passwords, or any interaction with the system redis.&lt;/p&gt;
&lt;p&gt;The code under test is meant to parse a web server log line, and then update some hit counts in redis.  Here's how our tests looked before:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nd"&gt;@patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;hit_counter.redis&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_write_to_redis_updates_appropriate_keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mock_redis&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;write_to_redis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;www.nowhere.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2013&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;54&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;2323&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertItemsEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;mock_redis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method_calls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;incr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;count|www.nowhere.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;incr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;count|www.nowhere.com|2013&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;incr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;count|www.nowhere.com|2013-11&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;incr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;count|www.nowhere.com|2013-11-14&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;incr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;count|www.nowhere.com|2013-11-14 16&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;incr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;count|www.nowhere.com|2013-11-14 16:54&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;incrby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;bytes|www.nowhere.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2323&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;incrby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;bytes|www.nowhere.com|2013&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2323&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;incrby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;bytes|www.nowhere.com|2013-11&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2323&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;incrby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;bytes|www.nowhere.com|2013-11-14&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2323&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;incrby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;bytes|www.nowhere.com|2013-11-14 16&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2323&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;incrby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;bytes|www.nowhere.com|2013-11-14 16:54&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2323&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And here's how they look after:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WriteToRedisTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unittest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;setUp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fake_redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redislite&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StrictRedis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patcher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;hit_counter.redis&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fake_redis&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patcher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;tearDown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patcher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_increments_all_counts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2013&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;54&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;expected_counts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;count|www.nowhere.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;count|www.nowhere.com|2013&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;count|www.nowhere.com|2013-11&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;count|www.nowhere.com|2013-11-14&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;count|www.nowhere.com|2013-11-14 16&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;count|www.nowhere.com|2013-11-14 16:54&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;expected_counts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fake_redis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;write_to_redis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;www.nowhere.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2323&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;expected_counts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fake_redis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;12&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_increments_all_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2013&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;54&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;expected_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;bytes|www.nowhere.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;# etc&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Look at that!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Instead of mocking the library, I'm mocking out a particular Redis instance, with a real, functioning redis that I have full control over&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I now no longer need to care about exactly how I use the redis API in my code -- no need to check whether I'm calling &lt;code&gt;incr&lt;/code&gt; or &lt;code&gt;incrby&lt;/code&gt;, I can just set an actual value before, and check the actual value after. It allows me to obey one of the key rules of testing, "test behaviour, don't test implementation".&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The approach only continued to deliver benefits.  The reason we were looking at this code was because we wanted to start setting some expiry times on keys.  As a result of using redislite, the tests for expiry came out really sane and readable:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_hourly_data_expires_after_one_week&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2013&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;54&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;write_to_redis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;www.thing.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ttl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fake_redis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;count|www.thing.com|2013-11-14 16&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertAlmostEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Imagine that test with mocks! &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertItemsEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;mock_redis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method_calls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;incr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;count|www.thing.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;incr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;count|www.thing.com|2013&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="c1"&gt;# ... &lt;/span&gt;
            &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;incr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;count|www.thing.com|2013-11-14 16&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;count|www.thing.com|2013-11-14 16&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected_expiry&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="c1"&gt;# and so on&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Ugh. And that's glossing over the complexities of timestamping -- the &lt;code&gt;assertAlmostEqual&lt;/code&gt; works nicely in the redislite tests, but here I'd have to either mock out &lt;code&gt;datetime&lt;/code&gt;, or pull all of the &lt;code&gt;call&lt;/code&gt; instances out of &lt;code&gt;.method_calls&lt;/code&gt; one by one...  Oh I just don't even want to think about it.&lt;/p&gt;
&lt;p&gt;So, remember kids, friends don't let friends use mocks when there's a better alternative around.  Definitely check out redislite if you ever find yourself writing some tests for a redis integration in your own codebase.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;[thanks to &lt;a href="https://twitter.com/nlhkabu"&gt;Nicole&lt;/a&gt; for prompting me to write this post]&lt;/em&gt;&lt;/p&gt;</summary><category term="Redis"></category><category term="Mocks"></category><category term="integration tests"></category></entry><entry><title>[OT] Autumn Leaves</title><link href="http://www.obeythetestinggoat.com/ot-autumn-leaves.html" rel="alternate"></link><published>2015-10-30T14:29:00+00:00</published><updated>2015-10-30T14:29:00+00:00</updated><author><name>Harry</name></author><id>tag:www.obeythetestinggoat.com,2015-10-30:ot-autumn-leaves.html</id><summary type="html">&lt;p&gt;To my usual readers that come hear for TDD, apologies, this is off-topic,
normal service will resume shortly.&lt;/p&gt;
&lt;p&gt;&lt;img src="/static/images/goatmastersvoice2.jpeg" width="200px"&gt;&lt;/img&gt;&lt;/p&gt;
&lt;p&gt;I was in an autumnal mood yesterday, and found myself listening to 3 different
versions of the jazz standard "Autumn Leaves", and since it's that sort of
season, I thought I'd share them with you.&lt;/p&gt;
&lt;p&gt;I realised I was surprisingly affected by the song, because actually I've been
listening to it, in different forms, for much longer than I thought.  Here are
the three that stand out, in reverse chronological order of my life encounters
with them.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/playlist?list=PLbYn653YTQ0IXlTXEOjNiyuXaUwz8nuAh"&gt;Youtube playlist&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="http://open.spotify.com/user/hjwp/playlist/77bEn7Lyta1GWdPTFbRUEq"&gt;Spotify playlist&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The first version is Eva Cassidy's.  The fact that I'm listening to folk
singers is not something I would ever have guessed when I was twenty, call it
my wife's bad influence, but I think this version is incredibly beautiful,
almost perfect.&lt;/p&gt;
&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/xXBNlApwh0c?list=PLbYn653YTQ0IXlTXEOjNiyuXaUwz8nuAh" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;

&lt;p&gt;(if you like the sounds of that, be sure to check out Eva Cassidy's rendition
of &lt;a href="https://www.youtube.com/watch?v=2rd8VktT8xY"&gt;"somewhere over the rainbow"&lt;/a&gt;,
which is absolutely heartbreaking, particularly if you know how she died)&lt;/p&gt;
&lt;p&gt;That was the version of the song I encountered in my 30s.  Here's the version from my
20s:&lt;/p&gt;
&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/fXypvAr0QrQ?list=PLbYn653YTQ0IXlTXEOjNiyuXaUwz8nuAh" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;

&lt;p&gt;Quite different!  The 
&lt;a href="https://www.youtube.com/watch?v=4qoTzDPDvE8"&gt;original Coldcut mix&lt;/a&gt; is well
worth checking out too.&lt;/p&gt;
&lt;p&gt;I was always surprised by how affecting I found this song, whether it was in my
increasingly-soppy, middle-aged 30s, or in my considerably cooler 20s, and searching
around yesterday I realised why.&lt;/p&gt;
&lt;p&gt;Autumn Leaves has been around for
&lt;a href="https://en.wikipedia.org/wiki/Autumn_Leaves_%281945_song%29"&gt;ages&lt;/a&gt;, but it's
actually originally a French song, the canonical rendition of which is by classic
French crooner Yves Montand, and this is the one I remember from my youth.&lt;/p&gt;
&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/_gZZbUrs6rU?list=PLbYn653YTQ0IXlTXEOjNiyuXaUwz8nuAh" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;

&lt;p&gt;Now we're back to my childhood in France, and apologies to non-French speakers, I'm not
sure if this will connect with y'all, but I find this one almost unbearable.
It's actually based on a poem by Jacques Prevert, one famous enough that we
studied it in school.  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://genius.com/Jacques-prevert-les-feuilles-mortes-annotated"&gt;Les feuilles mortes, Jacques Prévert&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In French, it's not "Autumn Leaves" though, it's "Dead Leaves" -- "Les Feuilles
Mortes". Already that sets a different tone, but it's the final stanza that I
think is so devastating:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Mais la vie sépare ceux qui s'aiment,&lt;/p&gt;
&lt;p&gt;Tout doucement, sans faire de bruit.&lt;/p&gt;
&lt;p&gt;Et la mer efface sur le sable,&lt;/p&gt;
&lt;p&gt;Les pas des amants désunis.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here's my rough translation:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;But life separates those who love each other,&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Gently, quietly,&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;And the sea washes away&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;the sandy footprints of lovers, now disunited.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;From French lessons you might remember you have to do an elision between the
"des" and the "amants", making it "desamants", anticipating the "desunis",
making it crushingly inevitable.  I think it's about the saddest thing in the
world.&lt;/p&gt;
&lt;p&gt;Enjoy!&lt;/p&gt;
&lt;p&gt;P.S. Everything's fine between me and my wife by the way! Don't worry.  Just inspired 
by the season...&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Image credit: &lt;a href="https://twitter.com/squirrelmuffins"&gt;kat squirrelmuffins&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</summary><category term="Autumn"></category><category term="Jazz"></category><category term="Poetry"></category></entry><entry><title>[OT] HiDPI on Ubuntu with a Samsung Ativ 9</title><link href="http://www.obeythetestinggoat.com/ot-hidpi-on-ubuntu-with-a-samsung-ativ-9.html" rel="alternate"></link><published>2014-12-14T23:39:00+00:00</published><updated>2014-12-14T23:39:00+00:00</updated><author><name>Harry</name></author><id>tag:www.obeythetestinggoat.com,2014-12-14:ot-hidpi-on-ubuntu-with-a-samsung-ativ-9.html</id><summary type="html">&lt;p&gt;This post isn't about TDD, but I just wanted to gather some notes into one place about how to get Hi-DPI working on Ubuntu.  I found other sources but my hope is that this will bring all the important tips into one place.&lt;/p&gt;
&lt;p&gt;I have a character flaw as regards overly shiny laptops, and when my golden Sony Vaio Z series finally packed in after 3 years of service, it was time to get a new one.  The sensible choice would have been a Thinkpad or a Galago from System 76, but then I saw &lt;a href="http://www.mobiletechreview.com/notebooks/Samsung-ATIV-Book-9-Plus.htm"&gt;this thing&lt;/a&gt;, and I had to have it.  It has a ridiculous 3200x1600 Hi-DPI screen, which I knew for sure was never going to work properly under Linux.  But then, GNU/Linux is &lt;em&gt;meant&lt;/em&gt; to be a bit rough on the desktop.  It's all part of the fun.&lt;/p&gt;
&lt;h1&gt;Hi-DPI on Ubuntu in brief&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Set two Gnome UI settings:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;gsettings set org.gnome.settings-daemon.plugins.xsettings overrides &amp;quot;{&amp;#39;Gdk/WindowScalingFactor&amp;#39;: &amp;lt;2&amp;gt;}&amp;quot;

gsettings set org.gnome.desktop.interface scaling-factor 2
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;(You can also install &lt;code&gt;gnome-tweak-tool&lt;/code&gt; to fiddle with these, cf the "Window" section for the most important one).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In Firefox and Thunderbird, open &lt;em&gt;about:config&lt;/em&gt; and change &lt;code&gt;layout.css.devPixelsPerPx&lt;/code&gt; to somewhere between 1.6 and 2&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;(Do &lt;/em&gt;not&lt;em&gt; touch &lt;code&gt;layout.css.dpi&lt;/code&gt;, that's a red herring).&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Then fix up your grub prompt and TTY consoles, more info on that below.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Credit to the ever-impressive &lt;a href="https://wiki.archlinux.org/index.php/HiDPI"&gt;Arch linux documentation&lt;/a&gt; for those tips.&lt;/p&gt;
&lt;h1&gt;Adjusting the grub boot menu to make it readable&lt;/h1&gt;
&lt;p&gt;You'll have noticed that the GRUB boot menu is in a ridiculously small font because of all our teeny-tiny pixels.  Fix it by generating a new font in 30-point:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sudo grub-mkfont -s 30 -o /boot/grub/DejaVuSansMono.pf2 /usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;DejaVu works well because it has all the glyphs for the line art, others may not work so well. &lt;/p&gt;
&lt;p&gt;Edit &lt;em&gt;/etc/default/grub&lt;/em&gt; and add a line saying &lt;code&gt;GRUB_FONT=/boot/grub/DejaVuSansMono.pf2&lt;/code&gt;, and run &lt;code&gt;update-grub&lt;/code&gt; to commit. There's more info &lt;a href="http://askubuntu.com/questions/11846/changing-the-default-grub-font"&gt;here&lt;/a&gt;. While you're at it, why not add a delightful background image to your boot screen?  Check out the &lt;a href="https://help.ubuntu.com/community/Grub2/Displays"&gt;Ubuntu wiki&lt;/a&gt; for details.&lt;/p&gt;
&lt;h1&gt;Adjusting your TTY console&lt;/h1&gt;
&lt;p&gt;One final thing that's pretty much unusable out of the box are the TTY consoles you get from pressing, eg, &lt;em&gt;CTRL+ALT+F1&lt;/em&gt;.  To change their font, you'll want to do a:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sudo dpkg-reconfigure console-setup
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;I picked the VGA font in 32x16, and it looks fine, if somewhat retro.&lt;/p&gt;
&lt;h1&gt;And that's your lot!&lt;/h1&gt;
&lt;p&gt;Loads of things will still look pretty wrong.  Chromium is a bit broken, but I don't use it enough to warrant a lengthy investigation. I haven't tried any non-gnome apps, but presumably they will suffer.&lt;/p&gt;
&lt;p&gt;Next challenge is getting reasonably good touchscreen support!&lt;/p&gt;
&lt;p&gt;I leave you with a screenshot...&lt;/p&gt;
&lt;p&gt;&lt;a href="/static/images/hidpi_desktop_screenshot.png"&gt;
    &lt;figure&gt;
        &lt;img src="/static/images/hidpi_desktop_screenshot.png" alt="screenshot" /&gt;
        &lt;figcaption&gt;So. Many. Pixels.&lt;/figcaption&gt;
    &lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;</summary><category term="Linux on the Desktop"></category></entry><entry><title>BDD with Django and Behave</title><link href="http://www.obeythetestinggoat.com/bdd-with-django-and-behave.html" rel="alternate"></link><published>2014-10-24T20:30:00+01:00</published><updated>2014-10-24T20:30:00+01:00</updated><author><name>Harry</name></author><id>tag:www.obeythetestinggoat.com,2014-10-24:bdd-with-django-and-behave.html</id><summary type="html">&lt;p&gt;I've started working on a new appendix for the book, which is meant to be an
intro to BDD.  Will you head on over to the online version of the book and
tell me what you think?&lt;/p&gt;
&lt;p&gt;&lt;a href="http://chimera.labs.oreilly.com/books/1234000000754/ape.html"&gt;BDD appendix on Chimera&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you're an existing reader, do you think it makes a good addition to the
book?  If you're a BDD expert, is there anything I'm getting hideously wrong?&lt;/p&gt;</summary><category term="Django"></category><category term="BDD"></category><category term="FT"></category></entry><entry><title>Decorators!</title><link href="http://www.obeythetestinggoat.com/decorators.html" rel="alternate"></link><published>2014-10-23T00:00:00+01:00</published><updated>2014-10-23T00:00:00+01:00</updated><author><name>Harry</name></author><id>tag:www.obeythetestinggoat.com,2014-10-23:decorators.html</id><summary type="html">&lt;p&gt;Someone recently wrote to me asking about decorators, and saying they found them a bit confusing.  Here's a post based on the email I replied to them with. &lt;/p&gt;
&lt;p&gt;The best way to understand decorators is to build a couple of them, so here are two examples for you to try out.  The first is in the Django world, the second is actually a simpler, pure-python one.&lt;/p&gt;
&lt;h2&gt;Challenge: build a decorator in a simple Django app&lt;/h2&gt;
&lt;p&gt;We've built a very basic todo lists app using Django.  It has views to deal with viewing lists, creating new lists, and adding to existing lists.  Two of these views end up doing some similar work, which is to retrieve a list object from the database based on its list ID:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;list_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;list_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;list_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;Item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;item_text&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;list_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/lists/&lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s1"&gt;/&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;list_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,))&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;view_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;list_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;list_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;list_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;list.html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;list&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;list_&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;(Full code &lt;a href="https://github.com/hjwp/book-example/blob/chapter_06/lists/views.py"&gt;here&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;This is a good use case for a decorator.&lt;/p&gt;
&lt;p&gt;A decorator can be used to extract duplicated work, and also to change the arguments to a function.  So we should be able to build a decorator that does the list-getting for us.  Here's the target:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nd"&gt;@get_list&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;list_&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;Item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;item_text&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;list_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/lists/&lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s1"&gt;/&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;list_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,))&lt;/span&gt;


&lt;span class="nd"&gt;@get_list&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;view_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;list_&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;list.html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;list&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;list_&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;So how do we build a decorator that does that?  A decorator is a function that takes a function, and returns another function that does a slightly modified version of the work the original function was doing.  We want our decorator to transform the simplified view functions we have above, into something that looks like the original functions.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;(you end up saying "function" a lot in any explanation of decorators...)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here's a template:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view_fn&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;decorated_view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="err"&gt;???&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;view_fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;decorated_view&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Can you get it working?  Thankfully, our code has tests, so they'll tell you when you get it right...&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;git clone -b chapter_06 https://github.com/hjwp/book-example
python3 manage.py test lists # dependencies: django 1.7
&lt;/pre&gt;&lt;/div&gt;


&lt;h2&gt;Some rules of thumb for decorators:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;they usually contain an "internal" function definition, which ends up being what the decorator returns.&lt;/li&gt;
&lt;li&gt;that internal function usually calls the original function.&lt;/li&gt;
&lt;li&gt;that internal function also needs to return something.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Decorators definitely are a bit brain-melting, so it may take a bit of effort to wrap your head around it.  Once you get the hang of them, they're dead useful though,&lt;/p&gt;
&lt;h2&gt;A simpler decorator challenge:&lt;/h2&gt;
&lt;p&gt;If you're finding it impossible, you could start with a simpler challenge...  say, building a decorator to make functions return an absolute value:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# this decorator currently does nothing&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;modified_fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;modified_fn&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;

&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;


&lt;span class="nd"&gt;@absolute&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;

&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;  &lt;span class="c1"&gt;# this will fail, get is passing!&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Try it out:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;git clone https://gist.github.com/2cc523b66d9c0fe41c4b.git deccy
python3 deccy/deccy.py
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Enjoy!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;[update 2014-10-23 at 3pm, see also &lt;a href="https://www.youtube.com/watch?v=Jmf48MJpLEM"&gt;@baroque, the decorating decorator decorator&lt;/a&gt;]&lt;/em&gt;&lt;/p&gt;</summary><category term="Python"></category><category term="decorators"></category><category term="Django"></category></entry><entry><title>Test-Driving a docker-based Postgres service using py.test</title><link href="http://www.obeythetestinggoat.com/test-driving-a-docker-based-postgres-service-using-pytest.html" rel="alternate"></link><published>2014-09-11T16:59:00+01:00</published><updated>2014-09-11T16:59:00+01:00</updated><author><name>Harry</name></author><id>tag:www.obeythetestinggoat.com,2014-09-11:test-driving-a-docker-based-postgres-service-using-pytest.html</id><summary type="html">&lt;p&gt;&lt;em&gt;[Cross-posted on the &lt;a href="https://blog.pythonanywhere.com/97/"&gt;PythonAnywhere blog&lt;/a&gt;]&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;We've been working on incorporating a Postgres database service into PythonAnywhere, and we decided to make it into a bit of a standalone project.  The shiny is that we're using Docker to containerise Postgres servers for our users, and while we were at it we thought we'd try a bit of a different approach to testing.  I'd be interested in feedback -- what do you like, what might you do differently?&lt;/p&gt;
&lt;h2&gt;Context:  A Docker-based Postgres service&lt;/h2&gt;
&lt;p&gt;The objective is to build a service that, on demand, will spin up a Docker container with Postgres running on it, and listening on a particular port.  The service is going to be controlled by a web API.  We've got Flask to run the web service, docker-py to control containers, and Ansible to provision servers.&lt;/p&gt;
&lt;h2&gt;A single loop of integrated tests&lt;/h2&gt;
&lt;p&gt;Normally we use a "double-loop" TDD process, with an outside loop of functional tests that use selenium to interact with our web app, and an inner loop of more isolated unit tests.  For our development of the Postgres service, we still have the outer loop of functional tests -- selenium tests that log into the site via a browser, and test the service from the perspective of the user -- clicking through the right buttons on our UI and seeing if they can get a console that connects to a new Postgres service.&lt;/p&gt;
&lt;p&gt;But for the inner loop we were in a green field -- this wasn't going to be another app in our monolithic Django project, we wanted it to be a standalone service, one that you could package up and use in another context.  It would provide all its services via an API, and need no knowledge of the rest of PythonAnywhere.  So how should we write the self-contained tests for this app?  Should it, in turn, have a double loop?  Relying on isolated unit tests only felt like a waste of time -- after all, the whole app was basically a thin wrapper that hooks up a web service to a series of Docker commands.  All boundaries.  Isolated unit tests would end up being all mocks.  And from a TDD-process point of view, because we'd never actually used docker-py before, we didn't know its API, so we wouldn't know what mocks to write before we'd actually decided what the code was going to look like, and tried it out.  And trying it out would involve either running one of the PythonAnywhere FTs (super-slow, so a tediously and onerous feedback loop), or with manual tests, with all the uncertainty that implies.&lt;/p&gt;
&lt;p&gt;So instead, it felt like starting with an intermediate-level layer of integrated tests might be best: we've already got our top-level UI layer full-stack tests in the form of functional tests.  The next level down was the API level -- does calling this particular URL on the API &lt;em&gt;actually&lt;/em&gt; give us a working container?&lt;/p&gt;
&lt;h2&gt;An example test&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_create_starts_container_with_postgres_connectable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;docker_cleanup&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;post_to_api_create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;port&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;

    &lt;span class="n"&gt;connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;psycopg2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;postgres&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pythonanywhere_helper&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;papwd&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;localhost&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Where&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;post_to_api_create&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;http://localhost:5000/api/create&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;admin_password&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;papwd&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;status&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;OK&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;So you can see that's a very integration-ey, end-to-end test -- it does a real POST request, to a place where it expects to see an actual webapp running, and it expects to see a real, connectable database spun up and ready for it.&lt;/p&gt;
&lt;p&gt;Now this test runs in about 10 seconds - not super-fast, like the milliseconds you might want a unit test to run in, but much faster than our FT, which takes 5 or 6 minutes. And, meanwhile, we can actually write this test first. To write an isolated, mocky test, we'd need to know the docker-py API already, and be sure that it was going to work, which we weren't.&lt;/p&gt;
&lt;p&gt;To illustrate this point, take a look at the difference between an early implementation and a later one:&lt;/p&gt;
&lt;h3&gt;A first implementation&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;USER_IMAGE_DOCKERFILE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="s1"&gt;FROM postgres&lt;/span&gt;
&lt;span class="s1"&gt;USER postgres&lt;/span&gt;
&lt;span class="s1"&gt;RUN /etc/init.d/postgresql start &amp;amp;&amp;amp; &lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;&lt;/span&gt;
&lt;span class="s1"&gt;    psql -c &amp;quot;CREATE USER pythonanywhere_helper WITH SUPERUSER PASSWORD &amp;#39;{hashed}&amp;#39;;&amp;quot;&lt;/span&gt;
&lt;span class="s1"&gt;CMD [&amp;quot;/usr/lib/postgresql/9.3/bin/postgres&amp;quot;, &amp;quot;-D&amp;quot;, &amp;quot;/var/lib/postgresql/9.3/main&amp;quot;, &amp;quot;-c&amp;quot;, &amp;quot;config_file=/etc/postgresql/9.3/main/postgresql.conf&amp;quot;]&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_user_dockerfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;admin_password&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;hashed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;md5&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;md5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;admin_password&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;pythonanywhere_helper&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;USER_IMAGE_DOCKERFILE&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;hashed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;hashed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_container_with_password&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;tempdir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tempfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mkdtemp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tempdir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Dockerfile&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;w&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_user_dockerfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tempdir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response_lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;image_finder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;r&amp;#39;Successfully built ([0-9a-f]+)&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image_finder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response_lines&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;image_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Image failed to build:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;{}&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response_lines&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_container&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;image_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;container&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;(These are some library functions we wrote, I won't show you the trivial flask app that calls them).&lt;/p&gt;
&lt;p&gt;This was one of our first attempts -- we needed to be able to customise the Postgres superuser password for each user, and our initial solution involved building a new image for each user, by generating and running a custom Dockerfile for them.&lt;/p&gt;
&lt;p&gt;We were never quite sure whether the Dockerfile voodoo was going to work, and we weren't really Postgres experts either, so having the high-level integration test, which actually tried to spin up a container and connect to the Postgres database that should be running inside it, was a really good way of getting to a solution that worked.&lt;/p&gt;
&lt;p&gt;Imagine what a more isolated test for this code might look like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nd"&gt;@patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;containers.docker&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_uses_dockerfile_to_build_new_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mock_docker&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;expected_dockerfile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;USER_IMAGE_DOCKERFILE&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;md5sekritpythonanywhere_helper&amp;#39;&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_dockerfile_contents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Dockerfile&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;expected_dockerfile&lt;/span&gt;

    &lt;span class="n"&gt;mock_docker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;side_effect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;check_dockerfile_contents&lt;/span&gt;

    &lt;span class="n"&gt;create_container_with_password&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;sekrit&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;mock_docker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;called&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

 &lt;span class="nd"&gt;@patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;containers.docker&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_creates_container_from_docker_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mock_docker&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;create_container_with_password&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;sekrit&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;mock_docker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_container&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert_called_once_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;mock_docker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;return_value&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;There's no way we could have written that test until we actually had a working solution.  And, on top of that, the test would have been totally useless when it came to evolving our requirements and our solution&lt;/p&gt;
&lt;h3&gt;A later implementation -- but minimal change to the main test&lt;/h3&gt;
&lt;p&gt;To give you an idea, here's what our current implementation looks like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start_new_container&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;storage_dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;requested_port&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;prep_storage_dir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;storage_dirname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;run_command_on_temporary_container_with_mounts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;chown&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;-R&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;postgres:postgres&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;POSTGRES_DIR&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;storage_dirname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;storage_dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;root&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;run_command_on_temporary_container_with_mounts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;bash&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;-c&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
            &lt;span class="n"&gt;INITIALISE_POSTGRES_AND_SET_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;storage_dirname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;storage_dirname&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;user_container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create_postgres_container&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;storage_dirname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;start_container_with_storage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;user_container&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;storage_dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;ports&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;POSTGRES_PORT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;requested_port&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;port_file_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;storage_dirname&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;w&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requested_port&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;requested_port&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;I won't bore you with the details of &lt;code&gt;run_command_on_temporary_container_with_mounts&lt;/code&gt;, but one way or another we realised that building separate images for each user wasn't going to work, and that instead we were going to want to have some permanent storage mounted in from outside of Docker, which would contain the Postgres data directory, and which would effectively "save" customisations like the user's password.&lt;/p&gt;
&lt;p&gt;So a radically different implementation, but look how little the main test changed:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;post_to_api_create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;storage_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;storage_dir&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;storage_dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9999&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;https://localhost/api/containers/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;storage_dir&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;storage_dir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;admin_password&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;OUR_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;port&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;verify&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_create_starts_container_with_postgres_connectable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;docker_cleanup&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;post_to_api_create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;6123&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# rest of test as before!&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And now imagine all the time we'd have had to spend rewriting mocks, if we'd decided to have isolated tests as well.&lt;/p&gt;
&lt;h2&gt;Aside: py.test observations&lt;/h2&gt;
&lt;p&gt;One py.test selling point is "less boilerplate". Notice that none of these tests are methods in a class, and there's no self variable.  On top of that, we just use &lt;code&gt;assert&lt;/code&gt; keywords, no complicated remembering of &lt;code&gt;self.assertIn&lt;/code&gt;, &lt;code&gt;self.assertIsNotNone&lt;/code&gt;,  and so on.  Absolutely loving that.&lt;/p&gt;
&lt;h3&gt;py.test fixtures&lt;/h3&gt;
&lt;p&gt;Another thing you may be interested in is the &lt;code&gt;docker_cleanup&lt;/code&gt; argument to the test.  py.test will magically look for a special fixture function named the same as that argument, and use it in the test.  Here's how it looks:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;docker&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;
&lt;span class="n"&gt;docker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;unix://var/run/docker.sock&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@pytest.fixture&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;docker_cleanup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;containers_before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;kill_new_containers&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;current_containers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;container&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;current_containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;container&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;containers_before&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;killing {}&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Names&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
                &lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addfinalizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kill_new_containers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;kill_new_containers&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The fixture function has a couple of jobs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;It adds a "finalizer" (the equivalent of unittest &lt;code&gt;addCleanup&lt;/code&gt; or &lt;code&gt;tearDown&lt;/code&gt;) which will run at the end of the tests, to kill any containers that have been started by the test&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It provides that same finalizer, and a helper method to identify new containers, to the tests that use the fixture, as a helper tool (I haven't showed any examples of that here though)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As it's illustrated here, there are no obvious advantages over the unittest &lt;code&gt;setUp/tearDown&lt;/code&gt; ideas, although you can see it would make it a little easier to share setup and cleanup code between tests in different files and tests.  There's a lot more to them, and if you really want to get #mindblown, go checkout out &lt;a href="http://pytest.org/latest/yieldfixture.html"&gt;pytest yield fixtures&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Incidentally, until I started using py.test I'd always associated "fixtures" with Django "fixtures", which basically meant serialized versions of model data, but really py.test is using the word in a more correct usage of the term, to mean "state that the world has to be in for the test to run properly".&lt;/p&gt;
&lt;h1&gt;The pros &amp;amp; cons of the "integrated-tests-only" workflow&lt;/h1&gt;
&lt;h4&gt;Pros:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Allowed us to experiment freely with an API that was new to us, and get feedback on whether it was &lt;em&gt;really&lt;/em&gt; working&lt;/li&gt;
&lt;li&gt;Allowed us to refactor code freely, extracting helper functions etc, without needing to rewrite mocky unit tests&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Cons:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Being end-to-end tests, they ran much slower than unit tests would - on the order of seconds, and later, a minute or two, once we grew from three or four tests to a dozen or two. And, on top of that...&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Being integrated tests, they're not designed to run on a development machine. Instead, each code change means pushing updated source up to the server using Ansible, restarting the control webapp, and then re-running the tests in an SSH session.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Because the tests call across a web API, the code being tested runs in a different process to he test code, meaning tracebacks aren't integrated into your test results.  Instead, you have to tail a logfile, and make sure you have logging set up appropriately.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusions and next steps&lt;/h2&gt;
&lt;p&gt;I can potentially imagine a time when we might start to see value in a layer of "real" unit tests... So far though, there's really no "business logic" that we could extract and write fast unit tests for. Or at least, there's no business logic that I identify as such, and I'd be very pleased for someone to come along and school me about it?&lt;/p&gt;
&lt;p&gt;On the other hand, I can definitely see a time where we might want to split out our tests for the web API from the tests for the Postgres and Docker stuff, and I can see value in a setup where a developer can run these tests locally rather than having to push code up to a dev box.  Vagrant and VirtualBox might be one solution, but, honestly, installing Docker and Postgres on a dev box doesn't feel that onerous either, as long as we know we'll be testing on a "real" box in CI. Or at least, it doesn't feel onerous until we start talking about my poor laptop with its paltry 120GB SSD.  No room here!&lt;/p&gt;
&lt;p&gt;And the bonus of being able to see honest-to-God tracebacks in your test run output feels like it might be worth it.&lt;/p&gt;
&lt;p&gt;But, overall, at this stage in development, given the almost total lack of "business logic" in our app, and given the fact that we were working with a new API and a new set of technologies -- I've found that doing without "real" unit tests has actually worked very well.&lt;/p&gt;</summary><category term="Docker"></category><category term="py.test"></category><category term="integrated tests"></category><category term="integration tests"></category></entry><entry><title>How to get Selenium to wait for page load after a click</title><link href="http://www.obeythetestinggoat.com/how-to-get-selenium-to-wait-for-page-load-after-a-click.html" rel="alternate"></link><published>2014-09-03T00:00:00+01:00</published><updated>2014-09-03T00:00:00+01:00</updated><author><name>Harry</name></author><id>tag:www.obeythetestinggoat.com,2014-09-03:how-to-get-selenium-to-wait-for-page-load-after-a-click.html</id><summary type="html">&lt;h2&gt;Oft-heard is the folorn cry...&lt;/h2&gt;
&lt;p&gt;Every so often you get bitten by a weird behaviour in one of your Selenium tests.  You tell it to click a link, and then you ask it something about the new page, and it returns you something from the old page:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;old_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find_element_by_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;my-id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
&lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find_element_by_link_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;my link&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;new_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find_element_by_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;my-id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;new_value&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;old_value&lt;/span&gt; &lt;span class="c1"&gt;## fails unexpectedly&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;em&gt;(There's another example, &lt;a href="http://chimera.labs.oreilly.com/books/1234000000754/ch20.html#_a_common_selenium_problem_race_conditions"&gt;in chapter 20 of my book&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;You scratch your head, and eventually conclude Selenium must be fetching the element from the old page.  "Why would it do that?!", you exclaim in a programmer-rage, "In real life, when you click on a link, you see the browser starts to load a new page, and you wait for it to load, right?  That's obviously what you'd want Selenium to do too, and it should be totally trivial to implement!"&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;"Selenium should just wait until the page has completed loading after you click!"&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find_element_by_link_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;my link&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# should just block until the next page has loaded&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;... with a sane timeout perhaps.  There's even a &lt;code&gt;document.readyState&lt;/code&gt; &lt;a href="https://dvcs.w3.org/hg/webdriver/raw-file/default/webdriver-spec.html#page-load-strategies-1"&gt;API&lt;/a&gt; for checking on whether a page has loaded!  Grrr...&lt;/p&gt;
&lt;p&gt;The thing is that, from the Selenium point of view, it's not that simple (and I'm grateful for David from Mozilla (&lt;a href="https://twitter.com/AutomatedTester"&gt;@AutomatedTester&lt;/a&gt;) for patiently explaining this to me, more than once.)&lt;/p&gt;
&lt;p&gt;You see, Selenium has no way of telling whether you've asked it to click on a "real" hyperlink that goes to a new URL, or whether the link goes to the same page, or whether the click is going to be intercepted by some sort of JavaScript to do some rich UI stuff on the same page.&lt;/p&gt;
&lt;p&gt;More than that, since Selenium webdriver has become more advanced, clicks are much more like "real" clicks, which has the benefit of making our tests more realistic, but it also means it's hard for Selenium to be able to track the impact that a click has on the browsers' internals -- it might try to poll the browser for its page-loaded status immediately after clicking, but that's open to a race condition where the browser was multitasking, hasn't quite got round to dealing with the click yet, and it gives you the &lt;code&gt;.readyState&lt;/code&gt; of the old page.&lt;/p&gt;
&lt;p&gt;So, instead, Selenium does its best.  The &lt;code&gt;implicitly_wait&lt;/code&gt; argument will at least put a little retry loop in if you try and fetch an element that doesn't exist on the old page:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;implicitly_wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;old_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find_element_by_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;thing-on-old-page&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
&lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find_element_by_link_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;my link&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;new_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find_element_by_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;thing-on-new-page&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="c1"&gt;# will block for 3 seconds until thing-on-new-page appears&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;new_value&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;old_value&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;But the problem comes when &lt;code&gt;#thing-on-new-page&lt;/code&gt; also exists on the old page.  So what to do?&lt;/p&gt;
&lt;p&gt;The "recommended" solution is an 
&lt;a href="https://selenium.googlecode.com/git/docs/api/py/webdriver_support/selenium.webdriver.support.expected_conditions.html"&gt;explicit wait&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;selenium.webdriver.common.by&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;By&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;selenium.webdriver.support.ui&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;WebDriverWait&lt;/span&gt; 
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;selenium.webdriver.support&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;expected_conditions&lt;/span&gt;

&lt;span class="n"&gt;old_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find_element_by_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;thing-on-old-page&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
&lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find_element_by_link_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;my link&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;WebDriverWait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;until&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;expected_conditions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text_to_be_present_in_element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;By&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;thing-on-new-page&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;expected new text&amp;#39;&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Several problems with that though:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;HIDEOUSLY UGLY &lt;a href="https://twitter.com/raganwald/status/504252812272754688"&gt;*&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;It's not generic --  even if I do write a nice wrapper, it's tedious to have to call it every time I click on a thing, specifying a different other thing to wait for each time&lt;/li&gt;
&lt;li&gt;And it won't work for the case when I want to check that some text stays the &lt;em&gt;same&lt;/em&gt; between page loads.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Really, I just want a reliable way of waiting until the page has finished loading after I click on a thing.  I totally understand that David and pals aren't going to provide that for me by default because they can't tell what's a Javascript click and what's a click that goes to a new page, but &lt;em&gt;I&lt;/em&gt; know.  But how to do it?&lt;/p&gt;
&lt;h3&gt;Some things that won't work&lt;/h3&gt;
&lt;p&gt;The naive attempt would be something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wait_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;condition_function&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;start_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;condition_function&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;Timeout waiting for {}&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;condition_function&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;click_through_to_new_page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;link_text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find_element_by_link_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;my link&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;page_has_loaded&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;page_state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute_script&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;return document.readyState;&amp;#39;&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; 
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;page_state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;complete&amp;#39;&lt;/span&gt;

    &lt;span class="n"&gt;wait_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page_has_loaded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;wait_for&lt;/code&gt; helper function is good, but unfortunately &lt;code&gt;click_through_to_new_page&lt;/code&gt; is open to the race condition where we manage to execute the script in the old page, before the browser has started processing the click, and &lt;code&gt;page_has_loaded&lt;/code&gt; just returns true straight away.&lt;/p&gt;
&lt;h3&gt;Our current working solution&lt;/h3&gt;
&lt;p&gt;Full credit to &lt;a href="https://twitter.com/ThomasMarks/status/506439068327358464"&gt;@ThomasMarks&lt;/a&gt; for coming up with this: if you keep some references to elements from the old page lying around, then they will become stale once the DOM refreshes, and stale elements cause selenium to raise a &lt;code&gt;StaleElementReferenceException&lt;/code&gt; if you try and interact with them.  So just poll one until you get an error.  Bulletproof!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;click_through_to_new_page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;link_text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find_element_by_link_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;my link&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;link_has_gone_stale&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# poll the link with an arbitrary call&lt;/span&gt;
            &lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find_elements_by_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;doesnt-matter&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;StaleElementReferenceException&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

    &lt;span class="n"&gt;wait_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;link_has_gone_stale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Or, here's a genericised, sanitized version of the same thing, based on comparing Selenium's internal "IDs" for an object, and made into a nice Pythonic context manager:&lt;/p&gt;
&lt;p&gt;[&lt;em&gt;update 2014-09-06 -- see the comments, it's possible that comparing ids is not as efffective as waiting for stale reference exceptions. Will investigate, but bewarned that YMMV for now.&lt;/em&gt;]&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;wait_for_page_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__enter__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;old_page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find_element_by_tag_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;page_has_loaded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;new_page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find_element_by_tag_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;new_page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;old_page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__exit__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;wait_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;page_has_loaded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And now we can do:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;wait_for_page_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find_element_by_link_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;my link&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And I think that might just be bulletproof!&lt;/p&gt;
&lt;h2&gt;And for bonus points...&lt;/h2&gt;
&lt;p&gt;(credit to Tommy Beadle for this solution)&lt;/p&gt;
&lt;p&gt;It turns out selenium has a built-in condition called &lt;code&gt;staleness_of&lt;/code&gt;, as well
as its own wait-for implementation.  Use them, alongside the &lt;code&gt;@contextmanager&lt;/code&gt;
decorator and the magical-but-slightly-scary &lt;code&gt;yield&lt;/code&gt; keyword, and you get:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;contextlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;contextmanager&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;selenium.webdriver.support.ui&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;WebDriverWait&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;selenium.webdriver.support.expected_conditions&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; \
    &lt;span class="n"&gt;staleness_of&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MySeleniumTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SomeFunctionalTestClass&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# assumes self.browser is a selenium webdriver&lt;/span&gt;

    &lt;span class="nd"&gt;@contextmanager&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wait_for_page_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;old_page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find_element_by_tag_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt;
        &lt;span class="n"&gt;WebDriverWait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;until&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;staleness_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;old_page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_stuff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# example use&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wait_for_page_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find_element_by_link_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;a link&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="c1"&gt;# nice!&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Note that this solution only works for "non-javascript" clicks, ie clicks that
will cause the browser to load a brand new page, and thus load a brand new HTML
body element.&lt;/p&gt;
&lt;p&gt;Let me know what you think!&lt;/p&gt;</summary><category term="Selenium"></category><category term="javascript"></category><category term="Ajax"></category><category term="functional tests"></category></entry><entry><title>The print book is available!</title><link href="http://www.obeythetestinggoat.com/the-print-book-is-available.html" rel="alternate"></link><published>2014-07-08T10:38:00+01:00</published><updated>2014-07-08T10:38:00+01:00</updated><author><name>Harry</name></author><id>tag:www.obeythetestinggoat.com,2014-07-08:the-print-book-is-available.html</id><summary type="html">&lt;p&gt;My copy of the print edition arrived in the post!&lt;/p&gt;
&lt;figure&gt;
    &lt;img src="/static/images/harry_with_new_book.jpg" alt="Harry holding his own book" /&gt;
    &lt;figcaption&gt;I am my own customer...&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(OK that actually happened last month.  This post is a little late.  I was distracted by Oscons and stuff!)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Anyway, that's it, it's officially available. If you've been reading the free online version and have been holding out until the print version comes out, now's the time!&lt;/p&gt;
&lt;p&gt;Or, if you've been waiting for the print version to come out before recommending the book to a friend, or buying a book for your local usergroup, or whatever it'll be, you know...&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.jdoqocy.com/click-7347114-11724864"&gt;Buy the book via O'Reilly&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.amazon.co.uk/gp/product/1449364829/ref=as_li_qf_sp_asin_tl?ie=UTF8&amp;amp;camp=1634&amp;amp;creative=6738&amp;amp;creativeASIN=1449364829&amp;amp;linkCode=as2&amp;amp;tag=obethetesgoa-21"&gt;Buy the book on amazon.com&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The online version will always be free and licensed under creative commons.  I'll just link to the acknowledgements chapter, since it really wouldn't have been possible without all the help I got:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://chimera.labs.oreilly.com/books/1234000000754/pr03.html"&gt;CC-licensed free version&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It's been a crazy 18 months. Happy reading everyone!&lt;/p&gt;</summary><category term="Book"></category><category term="physical book"></category><category term="dead trees"></category><category term="release"></category></entry><entry><title>Kent Beck on the limits of TDD</title><link href="http://www.obeythetestinggoat.com/kent-beck-on-the-limits-of-tdd.html" rel="alternate"></link><published>2014-06-03T11:28:00+01:00</published><updated>2014-06-03T11:28:00+01:00</updated><author><name>Harry</name></author><id>tag:www.obeythetestinggoat.com,2014-06-03:kent-beck-on-the-limits-of-tdd.html</id><summary type="html">&lt;p&gt;For anyone that's been following the DHH / #isTDDDead controversy, or
for anyone interested in what the limits of TDD are.  Kent Beck (the
godfather) had some really good reflections on what he was hoping to
learn from the discussions:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I'm puzzled by the limits of TDD--it works so well for algorithm-y, data-structure-y code. I love the feeling of confidence I get when I use TDD. I love the sense that I have a series of achievable steps in front of me--can't imagine the implementation? no problem, you can always write a test. I recognize that TDD loses value as tests take longer to run, as the number of possible faults per test failure increases, as tests become coupled to the implementation, and as tests lose fidelity with the production environment. How far out can TDD be pushed? Are there special cases where TDD works surprisingly well? Poorly? At what point is the cure worse than the disease? How can answers to any and all of these questions be communicated effectively? &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Read the full post on &lt;a href="https://www.facebook.com/notes/kent-beck/learning-about-tdd-the-purpose-of-istdddead/768162959883237"&gt;his facebook&lt;/a&gt;, and follow the 
&lt;a href="https://plus.google.com/events/cco30ri6dpkej4h4d8mejmat98o"&gt;next episode of #isTDDDead&lt;/a&gt;&lt;/p&gt;</summary><category term="TDD"></category><category term="#isTDDDead"></category><category term="limitations"></category></entry><entry><title>Book upgraded to Django 1.7!</title><link href="http://www.obeythetestinggoat.com/book-upgraded-to-django-17.html" rel="alternate"></link><published>2014-04-02T12:23:00+01:00</published><updated>2014-04-02T12:23:00+01:00</updated><author><name>Harry</name></author><id>tag:www.obeythetestinggoat.com,2014-04-02:book-upgraded-to-django-17.html</id><summary type="html">&lt;p&gt;In a (futile) attempt to future-proof the book, I decided to upgrade it to
Django 1.7. Here's how that went down.&lt;/p&gt;
&lt;h2&gt;Overview&lt;/h2&gt;
&lt;p&gt;Unsurprisingly, the biggest change was to do with migrations.  Like any new
change, my initial reaction was dislike, and I resented the new things, but I
think overall it's a definite improvment.&lt;/p&gt;
&lt;p&gt;In brief, here's what happened:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The new migrations framework means 'any' change to models needs a migration,
  or tests won't pass.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;This meant introducing the concept of migrations much earlier in the book; 
  in fact, at the same time as I introduce the ORM.  I resented this because
  it made the learning curve of the book steeper.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;On the other hand, because the new version of migrations essentially forces
  you to have them from the very beginning, I was able to drop an entire 
  chapter that was devoted to retrospectively building migrations after the
  first deployment, which included all sorts of checking out of old versions,
  and using &lt;code&gt;--fake&lt;/code&gt;, and so on.  So that's a big win.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Using step-by-step TDD also forces you to make more migrations than you really
  want to, if you're adding fields and constraints step-by-step.  It means I have
  to introduce the reader early on to the idea of replacing existing migrations
  too, another steepening of the learning curve.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here's some detail on the changes.&lt;/p&gt;
&lt;h2&gt;Migrations make the introduction of &lt;em&gt;models.py&lt;/em&gt; more complex&lt;/h2&gt;
&lt;p&gt;In chapter 5, where we build the first model, the narrative used to go:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Write a test &lt;/li&gt;
&lt;li&gt;See it fail&lt;/li&gt;
&lt;li&gt;Add code in &lt;em&gt;models.py&lt;/em&gt;, step by step&lt;/li&gt;
&lt;li&gt;Get the tests further, see a different failure&lt;/li&gt;
&lt;li&gt;Add more code in &lt;em&gt;models.py&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Get the tests passing&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now it goes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Write a test &lt;/li&gt;
&lt;li&gt;See it fail&lt;/li&gt;
&lt;li&gt;Add code in &lt;em&gt;models.py&lt;/em&gt;, step by step&lt;/li&gt;
&lt;li&gt;See a database error&lt;/li&gt;
&lt;li&gt;Create a migration&lt;/li&gt;
&lt;li&gt;See the tests get further, see a different failure&lt;/li&gt;
&lt;li&gt;Add more code in &lt;em&gt;models.py&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;See a database error&lt;/li&gt;
&lt;li&gt;Explain the concept of squashing migrations into one&lt;/li&gt;
&lt;li&gt;Delete the existing migration and re-create it. &lt;/li&gt;
&lt;li&gt;See the tests pass&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So you can see it's more complicated.  On the other hand, understanding
how Django gets from &lt;em&gt;models.py&lt;/em&gt; to the database is important.  I had been
just hand-waving and saying "use syncdb, and just delete the database if
anything goes wrong", so maybe it's better to address this stuff head-on,
rather than wait for a complicated later chapter.&lt;/p&gt;
&lt;p&gt;If you're curious, you can &lt;a href="http://chimera.labs.oreilly.com/books/1234000000754/ch05.html#_the_django_orm_amp_our_first_model"&gt;view the whole narrative here&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(If you're a Django core developer and you're reading this, I'd love to
hear your thoughts btw.  There's a few weeks before the book goes to print
yet, so there's still time to tell me I'm doing it all wrong!)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I still kinda wish I could have kept my nice shallow learning curve - 
I expended a lot of effort with the book, in trying to make sure concepts
are introduced one at a time and gradually, and now I feel I'm slightly
forced to lump two concepts onto the reader at the same time.  But, there's
clearly an upside.&lt;/p&gt;
&lt;h2&gt;But they save me from a fairly horrible chapter 13...&lt;/h2&gt;
&lt;p&gt;It was always going to be an unlucky chapter wasn't it.  Because I'd 
glossed over the concept of migrations until then, I would get the
readers to deploy their code to a server in chapter 8 or so, and
then code some new stuff, including a new database feature.&lt;/p&gt;
&lt;p&gt;Then I had a chapter 13 in which we would try and deploy to the staging
site, and see the new feature wouldn't work.  So then I had to explain
migrations, and go through this process:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Find the old commit that matches the point at which we did the last
   deployment, and check out the old version of &lt;em&gt;models.py&lt;/em&gt; from it.&lt;/li&gt;
&lt;li&gt;Do a &lt;code&gt;manage.py schemamigration&lt;/code&gt;, and create a migration to match live&lt;/li&gt;
&lt;li&gt;Check out the latest version of &lt;em&gt;models.py&lt;/em&gt;, and do another &lt;code&gt;schemamigration&lt;/code&gt;
   to get the migration we want to apply.&lt;/li&gt;
&lt;li&gt;Test it out locally.  Check out the old &lt;em&gt;models.py&lt;/em&gt; again, delete the databse,
   syncdb, then run &lt;code&gt;migrate 0001 --fake&lt;/code&gt;, then check out the new code, and
   run &lt;code&gt;migrate&lt;/code&gt;, check it works&lt;/li&gt;
&lt;li&gt;Adjust the deploy script to include &lt;code&gt;migrate 0001 --fake&lt;/code&gt; followed by a &lt;code&gt;migrate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Test deploying to staging... OK&lt;/li&gt;
&lt;li&gt;Deploy to live&lt;/li&gt;
&lt;li&gt;And, don't forget to now remove the &lt;code&gt;migrate 001 --fake&lt;/code&gt; from your deploy 
   script.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Ouch! Quite a lot of pain there!  Especially when you consider that the new
procedure is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Run the deploy script.  It just works, because we've had migrations all
   along.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;:-)&lt;/p&gt;
&lt;h2&gt;Other thoughts.&lt;/h2&gt;
&lt;p&gt;I found the fact that tests would fail if you didn't have migrations intriguing,
but unfortunately it's not something you can rely on.  For example, in chapter
12 I introduce a &lt;code&gt;unique_together&lt;/code&gt; constraint and test it thusly:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_duplicate_items_are_invalid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;list_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;Item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;list_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;bla&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertRaises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ValidationError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;list_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;bla&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;full_clean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;To get that passing, I just add my &lt;code&gt;unique_together&lt;/code&gt; constraint:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nb"&gt;list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;unique_together&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;list&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And at this point &lt;em&gt;Django doesn't warn me that I need a migration&lt;/em&gt;, because
the test is actually happening at the validation layer.&lt;/p&gt;
&lt;p&gt;I think that's a bit of a shame, but there's probably nothing to be done about
it.  It's all because the concepts of data validation and database integrity
constraints are separate in Django, even though their implementation in 
&lt;em&gt;models.py&lt;/em&gt; actually often happens in a single place...&lt;/p&gt;
&lt;h2&gt;One last thing...&lt;/h2&gt;
&lt;p&gt;&lt;img src="/static/images/makemigrations_screenshot_colour.png" /&gt;&lt;/p&gt;
&lt;p&gt;I love the pretty colours!&lt;/p&gt;</summary><category term="django"></category><category term="beta"></category><category term="migrations"></category></entry><entry><title>How to log exceptions to stderr in Django</title><link href="http://www.obeythetestinggoat.com/how-to-log-exceptions-to-stderr-in-django.html" rel="alternate"></link><published>2014-01-23T13:35:00+00:00</published><updated>2014-01-23T13:35:00+00:00</updated><author><name>Harry</name></author><id>tag:www.obeythetestinggoat.com,2014-01-23:how-to-log-exceptions-to-stderr-in-django.html</id><summary type="html">&lt;p&gt;Here's a common set of questions about Django:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How do I get django to log errors to stderr?&lt;/li&gt;
&lt;li&gt;Why can't I see Django exceptions in the console?&lt;/li&gt;
&lt;li&gt;How do I get Django to log exceptions?&lt;/li&gt;
&lt;li&gt;How to print debug messages in Django?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I know they're common because I've often found asking myself some kind of variant
on these questions, at some point or other.  Then I saw that, in Django 1.6/1.7, the
&lt;a href="https://docs.djangoproject.com/en/1.7/topics/logging/#django-s-default-logging-configuration"&gt;default logging configuration&lt;/a&gt; actually does send logging messages to the console&lt;/p&gt;
&lt;p&gt;So does this mean that exceptions in Django are going to start appearing in the console
then?  No.&lt;/p&gt;
&lt;h2&gt;Just because you wish for it doesn't make it so&lt;/h2&gt;
&lt;p&gt;I think my own difficulties with this issue stemmed from the fact that,
&lt;em&gt;obviously&lt;/em&gt;, in my mind, the thing you'd want to log would be exceptions, and
that, since Django will show us a clever debug page (if DEBUG is True), or a 
standard server 500 page, then I sort of assumed it would log that exception
as well. But it doesn't&lt;/p&gt;
&lt;p&gt;The reason I struggled with this because I wasn't clear on the fact that there are
actually two systems involved here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Django's logging system (based on the Python &lt;code&gt;logging&lt;/code&gt; module)&lt;/li&gt;
&lt;li&gt;Django's exception handling layer (middleware)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By default (as of Django 1.6), Django &lt;em&gt;is&lt;/em&gt; configured to send logging messages to
the console.  There are two gotchas however:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Django's default logging level is set to WARNING, which means any attempt to use
&lt;code&gt;logging.debug&lt;/code&gt; or &lt;code&gt;logging.info&lt;/code&gt; will fail&lt;/li&gt;
&lt;li&gt;Django has a middleware layer that automatically catches exceptions, and handles
them differently depending on whether you have &lt;code&gt;DEBUG = True&lt;/code&gt; or not, but it 
&lt;em&gt;doesn't&lt;/em&gt; explicitly log them.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Unit testing Django's logging config (if you want)&lt;/h2&gt;
&lt;p&gt;This is a blog about TDD, so let's write a test first.  It works by monkey-patching
in an extra view function into the project's url config (mwahahaha). The view then
tries to do various calls to, eg, &lt;code&gt;logging.debug&lt;/code&gt;, just we'd like to be able to in 
our real views.&lt;/p&gt;
&lt;p&gt;Finally it explodes with an exception, so we can test whether any information about
the exception ends up in the logs&lt;/p&gt;
&lt;p&gt;We mock out sys.stderr to pick up on what was actually sent to the console.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# test assumes django 1.6 and project called &amp;#39;myproj&amp;#39;&lt;/span&gt;
&lt;span class="c1"&gt;# also assumes this file is saved in myproj/myproj/test_logging.py,&lt;/span&gt;
&lt;span class="c1"&gt;# ie in the same folder as settings.py&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.test&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TestCase&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.conf.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;unittest.mock&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;patch&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;logging&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.http&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HttpResponse&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urlpatterns&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.settings&lt;/span&gt;  &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DEBUG&lt;/span&gt; &lt;span class="c1"&gt;# django.conf.settings are messed with by test runner&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;do_logging&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# dummy view that tries to log at all levels, and then raises an exception&lt;/span&gt;
    &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;debug logged&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;info logged&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;warning logged&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;critical&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;critical logged&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;arg&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;urls_with_logging_test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;r&amp;#39;^testlogging/$&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;do_logging&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LoggingTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_level&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;root_log_level&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;DEBUG&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLevelName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root_log_level&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;DEBUG&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLevelName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root_log_level&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;INFO&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


    &lt;span class="nd"&gt;@patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;myproj.urls.urlpatterns&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;urls_with_logging_test&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nd"&gt;@patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;myproj.test_logging.sys.stderr&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_logs_to_stderr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mock_stderr&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/testlogging/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;pass&lt;/span&gt;
        &lt;span class="n"&gt;written&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;mock_stderr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_args_list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;written&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;critical logged&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;written&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;warning logged&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;written&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;info logged&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;written&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEBUG&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;debug logged&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;written&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertNotIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;debug logged&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;written&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;arg&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;written&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Could not see exception in logs&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;do_logging&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;written&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Could not see traceback info&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h2&gt;Setting the base log level (if you want):&lt;/h2&gt;
&lt;p&gt;Here's what you need to add to &lt;em&gt;settings.py&lt;/em&gt; if you want to set the log
level lower, so that, say, &lt;code&gt;logging.info&lt;/code&gt; actually works.  You don't actually
need this to get the exception logging to work, but I discovered it while 
investigating this problem, so I thought I'd write it up here, since it's 
not obvious from the docs.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;LOGGING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;version&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;root&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;level&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;DEBUG&amp;#39;&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;DEBUG&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;INFO&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;I have a feeling someone will tell me that resetting the root log level is a 
silly thing to do, but it does work.  By all means enlighten me if this isn't
a good idea.&lt;/p&gt;
&lt;h2&gt;Using middleware to catch and log exceptions&lt;/h2&gt;
&lt;p&gt;Onto the real answer to this problem. Normally, to log an exception, you'd have
something like this&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;do_something_that_might_explode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Oh noes, it exploded&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And then &lt;code&gt;logging.exception&lt;/code&gt; will automagically print a full traceback as well
as our little message.&lt;/p&gt;
&lt;p&gt;But how to get this to just happen, by default, for any exceptions in our code?
The answer is what Django calls "middleware", code that can get run while handling
any request.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;logging&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExceptionLoggingMiddleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Exception handling request for &amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Cf the &lt;a href="https://docs.djangoproject.com/en/1.6/topics/http/middleware/#process-exception"&gt;Django docs on
middleware&lt;/a&gt;,
but that's really all you need!&lt;/p&gt;
&lt;p&gt;If this is saved to, say, &lt;em&gt;myproj/myproj/exception_logging_middleware.py&lt;/em&gt;, you would
then add it to your project in &lt;em&gt;settings.py&lt;/em&gt; by:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;MIDDLEWARE_CLASSES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.sessions.middleware.SessionMiddleware&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.middleware.common.CommonMiddleware&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.middleware.csrf.CsrfViewMiddleware&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.auth.middleware.AuthenticationMiddleware&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.messages.middleware.MessageMiddleware&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.middleware.clickjacking.XFrameOptionsMiddleware&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;myproj.exception_logging_middleware.ExceptionLoggingMiddleware&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;If you've actually used the test, you'll find it now passes.&lt;/p&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;In summary "how do I get Django to log all exceptions to stderr", which seems such
a straightforward question, actually does involve several different components:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the logging module, and Django's logging config&lt;/li&gt;
&lt;li&gt;Django's exception-handling middleware.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Whilst the former is where youd' go to choose &lt;em&gt;where&lt;/em&gt; things get logged to
(eg stderr or a file), and what the minimum log level is, the latter is the
place you actually need to go to if you want to log exceptions. &lt;/p&gt;</summary><category term="django"></category><category term="logging"></category><category term="exceptions"></category><category term="debugging"></category></entry><entry><title>Unit testing Ajax calls: if you're not using a mocking library, it's a world of pain</title><link href="http://www.obeythetestinggoat.com/unit-testing-ajax-calls-if-youre-not-using-a-mocking-library-its-a-world-of-pain.html" rel="alternate"></link><published>2013-10-19T16:05:00+01:00</published><updated>2013-10-19T16:05:00+01:00</updated><author><name>Harry</name></author><id>tag:www.obeythetestinggoat.com,2013-10-19:unit-testing-ajax-calls-if-youre-not-using-a-mocking-library-its-a-world-of-pain.html</id><summary type="html">&lt;div id="preamble"&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;&lt;em&gt;tl;dr: I found myself going through increasing contortions trying
to TDD some JavaScript code with Ajax in.  Once I started using
sinon.js, all the pain went away. Folks, don&amp;#8217;t try to roll your
own JavaScript mocks.&lt;/em&gt;&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;I&amp;#8217;ve been playing around with
&lt;a href="https://developer.mozilla.org/en-US/Persona"&gt;Mozilla Persona&lt;/a&gt;
as an authentication platform, and I knocked together this basic code to
interact with their API.  You can see it&amp;#8217;s quite dense, but fairly readable:&lt;/p&gt;&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;currentUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;{{ user.email }}&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;csrf_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;{{ csrf_token }}&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;loggedInUser&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;currentUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;onlogin&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;assertion&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/accounts/login&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;assertion&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;assertion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;csrfmiddlewaretoken&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;csrf_token&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reload&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logout&lt;/span&gt;&lt;span class="p"&gt;();});&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;onlogout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/accounts/logout&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;always&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reload&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;We call a function called &lt;code&gt;watch&lt;/code&gt;, passing it in an email address
string, and two callbacks for login and logout.  Login does a post,
refreshes the page if it succeeds, and calls a logout if it fails.
Logout just does a post and a refresh. Typical JavaScript, 3 levels
of nested callbacks, but it actually reads through quite well&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;So off I go on my merry way, planning to de-spike this code and
re-write it with TDD.  Mockin' libraries?  We don&amp;#8217;t need no stinkin'
mockin' libraries.  Im&amp;#8217;a roll my own, cos you can totes do that in
JavaScript:&lt;/p&gt;&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;initialize binds sign in button to navigator.id.request&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;requestWasCalled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;mockRequestFunction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;requestWasCalled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;mockNavigator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mockRequestFunction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;watch&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="nx"&gt;Superlists&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Accounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mockNavigator&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requestWasCalled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;check request not called before click&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;#id_login&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;click&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requestWasCalled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;check request called after click&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;So far, so slightly-awkward-but-not-too-bad. But look how badly things go wrong
once you start to try and write tests for more deeply nested callbacks:&lt;/p&gt;&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;initialize calls navigator.id.watch&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;current user&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;csrf token&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;urls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;login&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;login url&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;logout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;logout url&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;watchFunctionCalled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;mockWatchFunction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loggedInUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;check user&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onlogin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Superlists&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Accounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;submitAssertion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;check login fn&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;//&lt;b&gt;&amp;lt;1&amp;gt;&lt;/b&gt;&lt;/span&gt;
        &lt;span class="nx"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onlogout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Superlists&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Accounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logOut&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;check logout fn&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;//&lt;b&gt;&amp;lt;1&amp;gt;&lt;/b&gt;&lt;/span&gt;
        &lt;span class="nx"&gt;watchFunctionCalled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;mockNavigator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;watch&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mockWatchFunction&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="nx"&gt;Superlists&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Accounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mockNavigator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;watchFunctionCalled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;check watch function called&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class="colist arabic"&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;
You can see I ended up rewriting my anonymous callbacks as named functions
    in order to make them available to test.
&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;Now, was this the unit tests being useful, forcing me to break up my code into
smaller, more self-contained components?  I&amp;#8217;ll let you judge for yourself what
you think of the readability of the new code, compared to the old code:&lt;/p&gt;&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;accountUrls&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;csrfToken&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;personaNavigator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;initialize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
        &lt;span class="nx"&gt;accountUrls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;urls_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;csrfToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;#id_login&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;click&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="nx"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="nx"&gt;loggedInUser&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;onlogin&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;submitAssertion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;onlogout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;logOut&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;submitAssertion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;assertion&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nx"&gt;accountUrls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;login&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;assertion&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;assertion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;csrfmiddlewaretoken&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;csrfToken&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;Superlists&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Accounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;refreshPage&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;Superlists&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Accounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onLoginFailure&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;logOut&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accountUrls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logout&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;Superlists&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Accounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;refreshPage&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;onLoginFailure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;personaNavigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logout&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;refreshPage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reload&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Superlists&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;Accounts&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;initialize&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;logOut&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;logOut&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;onLoginFailure&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;onLoginFailure&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;refreshPage&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;refreshPage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;submitAssertion&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;submitAssertion&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;What the heck happened? At each stage I just tried to make sane,
self-contained unit tests, and I end up with this long and, I think,
much less readable code! Look at all that painful yanking of variables
up into a higher scope, and the contortions I&amp;#8217;m putting myself to give
them sensible names!  Look at all that mess on the Superlists.Accounts
namespace!&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_doctor_doctor_it_hurts_when_i_do_this"&gt;Doctor, Doctor, it hurts when I do this!&lt;/h3&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;So stop doing that.  Near the end of this adventure, I decided it was
time to investigate a proper mocking library. &lt;a href="http://sinonjs.org"&gt;sinon.js&lt;/a&gt;
seemed popular, and guess what&amp;#8201;&amp;#8212;&amp;#8201;it totally solved all my problems.&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;Look how much more readable the tests are:&lt;/p&gt;&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;initialize calls navigator.id.watch&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Superlists&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Accounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mockNavigator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mockNavigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;calledOnce&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;check watch function called&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;


&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;watch sees current user&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Superlists&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Accounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mockNavigator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;callArgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mockNavigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstCall&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nx"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loggedInUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;check user&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;onlogin does ajax post to login url&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Superlists&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Accounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mockNavigator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;onloginCallback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mockNavigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstCall&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;onlogin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;onloginCallback&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;check ajax request&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;POST&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;login&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;check url&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;onlogin sends assertion with middleware token&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Superlists&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Accounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mockNavigator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;onloginCallback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mockNavigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstCall&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;onlogin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;assertion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;browser-id assertion&amp;#39;&lt;/span&gt;
    &lt;span class="nx"&gt;onloginCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;assertion&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;requestBody&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;param&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;assertion&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;assertion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;csrfmiddlewaretoken&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;check POST data&amp;#39;&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;onlogin post failure should do navigator.id.logout &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;mockNavigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sinon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;spy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;Superlists&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Accounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mockNavigator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;onloginCallback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mockNavigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstCall&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;onlogin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sinon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fakeServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;respondWith&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;permission denied&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="nx"&gt;onloginCallback&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mockNavigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;called&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;should not logout yet&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;respond&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nx"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mockNavigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;called&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;should call logout&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;That last one is testing a callback nested fully 3 levels deep, and it&amp;#8217;s still
totally readable.  Sinon&amp;#8217;s &lt;code&gt;fakeServer&lt;/code&gt; makes checking callbacks on Ajax
requests a breeze.  Sure, there&amp;#8217;s still a bit too much boilerplate, the fact
that &lt;code&gt;.requestBody&lt;/code&gt; is URL-encoded and the only way to check send POST params
is a little annoying for example, but with this kind of testing I can get right
back to writing code the way I had it in the first place.&lt;/p&gt;&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;initialize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;#id_login&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;click&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="nx"&gt;loggedInUser&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;onlogin&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;assertion&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nx"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;login&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;assertion&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;assertion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;csrfmiddlewaretoken&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reload&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logout&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="nx"&gt;onlogout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;assertion&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;always&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reload&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;Perfectly readable.&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_but_isn_8217_t_tdd_supposed_to_make_you_break_up_monolithic_code_blocks"&gt;But isn&amp;#8217;t TDD supposed to make you break up monolithic code blocks?&lt;/h3&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;So that&amp;#8217;s a question - one of the supposed advantages of unit testing
is that, when you find yourself struggling to write tests, you often
find yourself re-writing your code so that is uses several, smaller,
self-contained, more easily testable components, and your code is
improved as a result.&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;In this case though, my code was definitely not being improved&amp;#8201;&amp;#8212;&amp;#8201;or
at least, that&amp;#8217;s what I think.  Would anyone disagree?&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;Ultimately I think it was a matter of using the wrong tool for the job.
Once I had a decent mocking library, I was able to get back to
good-looking code and good-looking tests.&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;Another thing is that nested callbacks are just quite a natural part
of client-side JavaScript.  GUI / UI / Async code just isn&amp;#8217;t the same
as server-side code I guess, so I shouldn&amp;#8217;t be surprise if it follows
slightly different patterns of readability.  What do you think?&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;Anyway folks, if you&amp;#8217;re not using a mocking library to test your Ajax code,
you should definitely try one.  &lt;a href="http://sinonjs.org"&gt;sinon.js&lt;/a&gt; worked
well for me, there are others out there in the wonderfully diverse
JS testing ecosystem.&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;More info on Mozilla Persona, unit testing javascript and TDD in
my book, &lt;a href="http://chimera.labs.oreilly.com/books/1234000000754/ch14.html"&gt;chapter
14 of which&lt;/a&gt; prompted this write-up.  You should check it out. It&amp;#8217;s
got a great joke with Henry Ford in it, which I&amp;#8217;m particularly proud
of.&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
</summary><category term="JavaScript"></category><category term="Qunit"></category><category term="sinon"></category><category term="mocking"></category><category term="Ajax"></category><category term="dependency injection"></category></entry><entry><title>Testing Django Class-Based (Generic) Views</title><link href="http://www.obeythetestinggoat.com/testing-django-class-based-generic-views.html" rel="alternate"></link><published>2013-09-27T16:30:00+01:00</published><updated>2013-09-27T16:30:00+01:00</updated><author><name>Harry</name></author><id>tag:www.obeythetestinggoat.com,2013-09-27:testing-django-class-based-generic-views.html</id><summary type="html">&lt;div id="preamble"&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;This blog post is a first rough draft of a planned appendix to my
&lt;a href="http://www.obeythetestinggoat.com/pages/book.html"&gt;book&lt;/a&gt;.  It follows
on from Chapter 9, which is all about forms and validation. You can
&lt;a href="http://chimera.labs.oreilly.com/books/1234000000754/ch09.html"&gt;take a look at it
here&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;If you want to check out the code to have a play with the examples, you&amp;#8217;ll
find them on GitHub under the
&lt;a href="https://github.com/hjwp/book-example/tree/chapter_09"&gt;chapter_09 branch&lt;/a&gt; and
the
&lt;a href="https://github.com/hjwp/book-example/tree/appendix_II"&gt;appendix_II branch&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;As you&amp;#8217;ll see, the content starts out sounding a lot like a "proper" chapter
for a book, and turns into more of a blog post and request for comments.
Please do let me know what you think!&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect4"&gt;
&lt;h5 id="_update_2013_10_05"&gt;Update 2013-10-05&lt;/h5&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;There&amp;#8217;s been some interesting discussion with minds much greater than my own,
such as those of Messrs Russell Keith-MaGee and Trey Hunner, which, for one
reason or another, has taken place as line comments on github. Do check it out:&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;&lt;a href="https://github.com/hjwp/www.obeythetestinggoat.com/commit/c5857b416e404c9af5a9205fbf1b10dccaf8161d"&gt;https://github.com/hjwp/www.obeythetestinggoat.com/commit/c5857b416e404c9af5a9205fbf1b10dccaf8161d&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect4"&gt;
&lt;h5 id="_update_2013_10_16"&gt;Update 2013-10-16&lt;/h5&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;My basic conclusion for how to test CBGVs is now: make sure you have lots of
short, single-assertion tests for your views, and it will be easy to adjust
to using class-based views from function-based ones, and vice-versa.  Cf the
re-cap at the end of chapter 11:&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;&lt;a href="http://chimera.labs.oreilly.com/books/1234000000754/ch11.html#_using_the_existing_lists_item_form_in_the_list_view"&gt;http://chimera.labs.oreilly.com/books/1234000000754/ch11.html#_using_the_existing_lists_item_form_in_the_list_view&lt;/a&gt;
(scroll right to the end)&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;And the updated version of this post / appendix:&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;&lt;a href="http://chimera.labs.oreilly.com/books/1234000000754/apb.html"&gt;http://chimera.labs.oreilly.com/books/1234000000754/apb.html&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_appendix_ii_django_class_based_views"&gt;Appendix II: Django Class-based views&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;This appendix follows on from Chapter 9, in which we implemented Django forms
for validation, and refactored our views.  By the end of that chapter, our
views were still using functions.&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;The new shiny in the Django world, however, is class-based views. In this
chapter, we&amp;#8217;ll refactor our application to use them instead of view functions.
More specifically, we&amp;#8217;ll have a go at using class-based &lt;em&gt;generic&lt;/em&gt; views.&lt;/p&gt;&lt;/div&gt;
&lt;div class="admonitionblock"&gt;
&lt;table&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;div class="title"&gt;Warning&lt;/div&gt;
&lt;/td&gt;
&lt;td class="content"&gt;this appendix is currently more of a blog post / request for comments
than a final appendix.  Use at your own peril.&lt;/td&gt;
&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_class_based_generic_views"&gt;Class-based generic views&lt;/h3&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;It&amp;#8217;s worth making a distinction at this point, between class-based views and
class-based &lt;em&gt;generic&lt;/em&gt; views.  Class-based views are just another way of
defining view functions.  They make few assumptions about what your views will
do, and they offer one major benefit over view functions, which is that they
can be subclassed.  This comes, arguably, at the expense of being less readable
than traditional function-based views.  The main use case for &lt;em&gt;plain&lt;/em&gt;
class-based views is when you have several views that re-use the same logic.
We want to obey the DRY principle. With function-based views, you would use
helper functions or decorators.  The theory is that using a class structure may
give you a more elegant solution.&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;Class-based &lt;em&gt;generic&lt;/em&gt; views are class-based views that attempt to provide
ready-made solutions to common use cases:  fetching an object from the
database and passing it to a template, fetching a list of objects, saving
user input from a POST request using a ModelForm, and so on.  These sound very
much like our use cases, but as we&amp;#8217;ll soon see, the devil is in the detail.&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;I should say at this point that I&amp;#8217;ve not used either kind of class-based views
much. I can definitely see the sense in them, and there are potentially many
use cases in Django apps where CBGVs would fit in perfectly. However, as soon
as your use case is slightly outside the basics&amp;#8201;&amp;#8212;&amp;#8201;as soon as you have more
than one model you want to use, for example, I&amp;#8217;ve found that using class-based
views becomes much more complicated, and you end up with code that&amp;#8217;s harder to
read than a classic view function.&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;Still, because we&amp;#8217;re forced to use a lot of the customisation options for
class-based views, implementing them in this case can teach us a lot about
how they work, and how we can unit tests them.&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;My hope is that the same unit tests we use for function-based views should
work just as well for class-based views.  Let&amp;#8217;s see how we get on.&lt;/p&gt;&lt;/div&gt;
&lt;div class="sect3"&gt;
&lt;h4 id="_the_home_page_as_a_formview"&gt;The home page as a FormView&lt;/h4&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;Our home page just displays a form on a template:&lt;/p&gt;&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;home_page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;home.html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;form&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ItemForm&lt;/span&gt;&lt;span class="p"&gt;()})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;&lt;a href="https://docs.djangoproject.com/en/1.5/ref/class-based-views/"&gt;Looking through
the options&lt;/a&gt;, Django has a generic view called &lt;code&gt;FormView&lt;/code&gt;&amp;#8201;&amp;#8212;&amp;#8201;let&amp;#8217;s see how that
goes:&lt;/p&gt;&lt;/div&gt;
&lt;div class="listingblock sourcecode"&gt;
&lt;div class="title"&gt;lists/views.py (ch21l001)&lt;/div&gt;
&lt;div class="content"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.views.generic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FormView&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HomePageView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FormView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;template_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;home.html&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;form_class&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ItemForm&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;We tell it what template we want to use, and which form. Then, we
just need to update &lt;em&gt;urls.py&lt;/em&gt;, replacing the line that used to say
&lt;code&gt;lists.views.home_page&lt;/code&gt;:&lt;/p&gt;&lt;/div&gt;
&lt;div class="listingblock sourcecode"&gt;
&lt;div class="title"&gt;superlists/urls.py (ch21l002)&lt;/div&gt;
&lt;div class="content"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;    &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;r&amp;#39;^$&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HomePageView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;as_view&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;home&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;And the tests all check out! That was easy..&lt;/p&gt;&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre&gt;&lt;code&gt;$ python3 manage.py test lists
Creating test database for alias 'default'...
......................
 ---------------------------------------------------------------------
Ran 22 tests in 0.134s

OK
Destroying test database for alias 'default'...

$ python3 manage.py test functional_tests
Creating test database for alias 'default'...
....
 ---------------------------------------------------------------------
Ran 4 tests in 15.160s

OK
Destroying test database for alias 'default'...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;So far so good. We&amp;#8217;ve replaced a 1-line view function with a 2-line class,
but it&amp;#8217;s still very readable. This would be a good time for a commit&amp;#8230;&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect3"&gt;
&lt;h4 id="_using_form_valid_to_customise_a_createview"&gt;Using form_valid to customise a CreateView&lt;/h4&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;Next we have a crack at the view we use to create a brand new list, currently
the &lt;code&gt;new_list&lt;/code&gt; function.  Looking through the possible CBGVs, we
probably want a &lt;code&gt;CreateView&lt;/code&gt;, and we know we&amp;#8217;re using the &lt;code&gt;ItemForm&lt;/code&gt; class,
so let&amp;#8217;s see how we get on with them, and whether the tests will help us:&lt;/p&gt;&lt;/div&gt;
&lt;div class="listingblock sourcecode"&gt;
&lt;div class="title"&gt;lists/views.py&lt;/div&gt;
&lt;div class="content"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NewListView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CreateView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;form_class&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ItemForm&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;new_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ItemForm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_valid&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="nb"&gt;list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;Item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;home.html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;form&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;I&amp;#8217;m going to leave the old view function in &lt;em&gt;views.py&lt;/em&gt;, so that we can copy
code across from it.  We can delete it once everything is working.  It&amp;#8217;s
harmless as soon as we switch over the URL mappings, this time in:&lt;/p&gt;&lt;/div&gt;
&lt;div class="listingblock sourcecode"&gt;
&lt;div class="title"&gt;lists/urls.py&lt;/div&gt;
&lt;div class="content"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;    &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;r&amp;#39;^new$&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NewListView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;as_view&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;new_list&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;Now running the tests gives 3 errors:&lt;/p&gt;&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre&gt;&lt;code&gt;$ python3 manage.py test lists
Creating test database for alias 'default'...
...................EEE
======================================================================
ERROR: test_redirects_after_POST (lists.tests.test_views.NewListTest)
 ---------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/harry/Dropbox/book/source/appendix_II/superlists/lists/tests/test_views.py", line 33, in test_redirects_after_POST
    data={'text': 'A new list item'}
    [...]
  File "/usr/local/lib/python3.3/dist-packages/django/forms/models.py", line 370, in save
    fail_message, commit, construct=False)
  File "/usr/local/lib/python3.3/dist-packages/django/forms/models.py", line 87, in save_instance
    instance.save()
  File "/home/harry/Dropbox/book/source/appendix_II/superlists/lists/models.py", line 26, in save
    self.full_clean()
  File "/usr/local/lib/python3.3/dist-packages/django/db/models/base.py", line 926, in full_clean
    raise ValidationError(errors)
django.core.exceptions.ValidationError: {'list': ['This field cannot be null.']}

======================================================================
ERROR: test_saving_a_POST_request (lists.tests.test_views.NewListTest)
 ---------------------------------------------------------------------
[...]
django.core.exceptions.ValidationError: {'list': ['This field cannot be null.']}

======================================================================
ERROR: test_validation_errors_sent_back_to_home_page_template (lists.tests.test_views.NewListTest)
 ---------------------------------------------------------------------
[...]
django.template.base.TemplateDoesNotExist: No template names provided

 ---------------------------------------------------------------------
Ran 22 tests in 0.114s

FAILED (errors=3)
Destroying test database for alias 'default'...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;TODO: talk through decoding traceback.&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;Let&amp;#8217;s start with the third&amp;#8201;&amp;#8212;&amp;#8201;maybe we can just add the template?&lt;/p&gt;&lt;/div&gt;
&lt;div class="listingblock sourcecode"&gt;
&lt;div class="title"&gt;lists/views.py&lt;/div&gt;
&lt;div class="content"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NewListView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CreateView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;form_class&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ItemForm&lt;/span&gt;
    &lt;span class="n"&gt;template_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;home.html&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;That gets us down to just two failures.  They&amp;#8217;re both to do with dealing
with &lt;em&gt;valid&lt;/em&gt; POST requests.  CBGVs that deal with forms want you to put
any custom code for valid forms in a method called &lt;code&gt;form_valid&lt;/code&gt;.  We can
just copy across some of the code from the old view function:&lt;/p&gt;&lt;/div&gt;
&lt;div class="listingblock sourcecode"&gt;
&lt;div class="title"&gt;lists/views.py&lt;/div&gt;
&lt;div class="content"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NewListView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CreateView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;template_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;home.html&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;form_class&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ItemForm&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;form_valid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;Item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cleaned_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;That gets us a pass!&lt;/p&gt;&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre&gt;&lt;code&gt;$ python3 manage.py test lists
Ran 22 tests in 0.117s
OK
$ python3 manage.py test functional_tests
Ran 4 tests in 15.157s
OK&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;And we can even save two lines (DRY) by taking advantage of the real point of
CBVs: inheritance!&lt;/p&gt;&lt;/div&gt;
&lt;div class="listingblock sourcecode"&gt;
&lt;div class="title"&gt;lists/views.py&lt;/div&gt;
&lt;div class="content"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NewListView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CreateView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HomePageView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;form_valid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;Item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cleaned_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/lists/&lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s1"&gt;/&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;And all the tests still pass.&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;How does it compare to the old version? I&amp;#8217;d say that&amp;#8217;s not bad.   We save some
boilerplate code, and the view is still fairly legible.  So far, I&amp;#8217;d say we&amp;#8217;ve
got one point for CBGVs, and one draw.&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect3"&gt;
&lt;h4 id="_a_more_complex_view_to_handle_both_viewing_and_adding_to_a_list"&gt;A more complex view to handle both viewing and adding to a list&lt;/h4&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;This took me &lt;em&gt;several&lt;/em&gt; attempts.  And I have to say that, although the tests
told me when I got it right, they didn&amp;#8217;t really help me to figure out the
steps to get there&amp;#8230;  Mostly it was just trial and error, hacking about
in functions like &lt;code&gt;get_context_data&lt;/code&gt;, &lt;code&gt;get_form_kwargs&lt;/code&gt; and so on.&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;One thing I did do which improved my codebase was to add a new unit test:&lt;/p&gt;&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="title"&gt;lists/tests/test_views.py&lt;/div&gt;
&lt;div class="content"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ListViewTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_list_view_displays_form_for_existing_lists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;correct_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/lists/&lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s1"&gt;/&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;correct_list&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,))&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertIsInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;form&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;ExistingListItemForm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;It&amp;#8217;s another good example of the "each test should test one thing" heuristic:
that check on the form class could very easily have been tacked onto the end
of a different test, but having it separate means I&amp;#8217;m immediately told exactly
what&amp;#8217;s wrong, rather than potentially having the error masked by an earlier
failure.&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;TODO: consider moving this test into ch. 9?&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;Anyway, after much hacking and swearing, this is the solution I eventually got
to work:&lt;/p&gt;&lt;/div&gt;
&lt;div class="listingblock sourcecode"&gt;
&lt;div class="title"&gt;lists/views.py&lt;/div&gt;
&lt;div class="content"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ViewAndAddToList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CreateView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SingleObjectMixin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;template_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;list.html&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;
    &lt;span class="n"&gt;form_class&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ExistingListItemForm&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_form&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;form_class&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_object&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;POST&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="s1"&gt;&amp;#39;list&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;form_class&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;I also had to add a &lt;code&gt;get_absolute_url&lt;/code&gt; on the &lt;code&gt;Item&lt;/code&gt; class:&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;(I did try to use &lt;code&gt;get_form_kwargs&lt;/code&gt; instead of &lt;code&gt;get_form&lt;/code&gt;, but it didn&amp;#8217;t want
to work for me.  Perhaps some CBGV expert out there has a neater solution??)&lt;/p&gt;&lt;/div&gt;
&lt;div class="listingblock sourcecode"&gt;
&lt;div class="title"&gt;lists/models.py&lt;/div&gt;
&lt;div class="content"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_absolute_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_absolute_url&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class="sect4"&gt;
&lt;h5 id="_compare_old_and_new"&gt;Compare old and new&lt;/h5&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;Let&amp;#8217;s see the old version for comparison?&lt;/p&gt;&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;view_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;list_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;list_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;POST&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ExistingListItemForm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;list&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_valid&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ExistingListItemForm&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;list.html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;list&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;form&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;Not a great improvement.  Same number of lines of code, 15.  If anything, the
function version is better because it has one more line of whitespace. And it&amp;#8217;s
definitely more readable.&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_best_practices_for_unit_testing_cbgvs"&gt;Best practices for unit testing CBGVs?&lt;/h3&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;As I was working through this, I felt like my "unit" tests were sometimes a
little too high-level.  They told me whether I was getting things right or
wrong, but they didn&amp;#8217;t offer many clues on exactly how to fix things.&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;I occasionally wondered whether there might be some mileage in a test that
was closer to the implementation&amp;#8201;&amp;#8212;&amp;#8201;something like this:&lt;/p&gt;&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_as_cbv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;our_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;view&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ViewAndAddToList&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;our_list&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_object&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;our_list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;But the problem is that it requires a lot of knowledge of the internals of
Django CBVs to be able to do the right test setup for these kinds of tests.
And you still end up getting very confused by the complex inheritance
hierarchy.&lt;/p&gt;&lt;/div&gt;
&lt;div class="paragraph"&gt;&lt;p&gt;I&amp;#8217;d be interested to hear how other people out there are testing their CBVs?&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
</summary><category term="Django"></category><category term="class-based-views"></category><category term="unit testing"></category></entry><entry><title>Unit testing fabric scripts for deployment</title><link href="http://www.obeythetestinggoat.com/unit-testing-fabric-deployment-scripts.html" rel="alternate"></link><published>2013-09-19T07:30:00+01:00</published><updated>2013-09-19T07:30:00+01:00</updated><author><name>Harry</name></author><id>tag:www.obeythetestinggoat.com,2013-09-19:unit-testing-fabric-deployment-scripts.html</id><summary type="html">&lt;p&gt;In chapter 8 of my &lt;a href="http://www.obeythetestinggoat.com/pages/book.html"&gt;book&lt;/a&gt; I introduce a fabric script as a way of automating the deployment of our example app.  You can see the section where I do so &lt;a href="http://chimera.labs.oreilly.com/books/1234000000754/ch08.html#_automating_deployment_with_fabric"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;After 7 chapters of building everything step-by-step with TDD and detailed unit tests, this fairly large script leaps out fully-formed, It prompted one of my readers to write in (Thanks Nick):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It didn't work the first time I used it (I retype, rather than copy-paste, to better understand the code. Occasional typos are inevitable, and unit testing is a great way to catch these small errors). With the rest of the project, there was substantial testing that went into the creation of each source file, meaning small errors were swiftly located and corrected. However, running the fabfile often caused errors in code that wasn't tested, which took me much longer to find a fix.&lt;/p&gt;
&lt;p&gt;There should be a way to test the deployment script as thoroughly as the rest of the project code. Especially considering how critical deployment is to a web app, and how often it will likely need to be done, maintaining error-free consistency in this process seems like a critical part of the workflow, and worth writing tests for.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Nick is right isn't he? There's a bit of a disjoint here. Surely we need some kind of testing for our deployment scripts? &lt;/p&gt;
&lt;p&gt;Admittedly in this chapter I'm explaining how to run Selenium tests against a staging site to make sure that our deployment procedure works, so our deployment script &lt;em&gt;is&lt;/em&gt; tested indirectly, but everywhere else in the book I've &lt;em&gt;also&lt;/em&gt; written unit tests for all my code.&lt;/p&gt;
&lt;p&gt;At PythonAnywhere, we decided not to write tests for our fabric scripts, and we now regret it.  There's about 3000 lines of messy code in there, which we're scared of refactoring.&lt;/p&gt;
&lt;p&gt;But how to write some kind of low-level tests for a fabric script? Here's a sample function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_update_virtualenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source_folder&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;virtualenv_folder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source_folder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;../virtualenv&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;virtualenv_folder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;bin&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;pip&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt; &lt;span class="c1"&gt;#&amp;lt;11&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;virtualenv --python=python3.3 &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;virtualenv_folder&lt;/span&gt;&lt;span class="p"&gt;,))&lt;/span&gt;
    &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s1"&gt;/bin/pip install -r &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s1"&gt;/requirements.txt&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;virtualenv_folder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;source_folder&lt;/span&gt;
    &lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;It seems to me there's no great ways of testing this sort of stuff?  Here's three I can think of:&lt;/p&gt;
&lt;h3&gt;1. Mock out Fabric&lt;/h3&gt;
&lt;p&gt;You could mock out the Fabric API, and write a bunch of tests that say things like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;mock_run&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method_calls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;virtualenv --python=python3.3 /path/to/virtualenv&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/path/to/virtualenv/bin/pip install -r /path/to/my/folder/requirements.txt&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;But I hate that kind of unit test!  It's just duplicating your code with a bunch of mocks.  It's not that it has &lt;em&gt;no&lt;/em&gt; value -- it's a bit like double-entry accounting, so it might help Nick or I catch the occasional typo -- but it doesn't really feel like testing, you know?  It's so tightly coupled to the implementation as to be almost identical, and we'd definitely be testing implementation rather than behaviour.&lt;/p&gt;
&lt;p&gt;I mean, would you really test-drive writing your fabric scripts using these kinds of tests?&lt;/p&gt;
&lt;h3&gt;2. Monkeypatch fabric to work in /tmp&lt;/h3&gt;
&lt;p&gt;You could write a sort of hacked-up integration test, which redirects all the fabric calls to a temp folder on the machine you're using for testing, something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# assumes we have some recognisable prefix for path given&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;mock_run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd_given&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;cmd_redirected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cmd_given&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;path_prefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tmp_path&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;path_prefix&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# use fabric local() function to run command locally instead&lt;/span&gt;
    &lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd_redirected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;#...&lt;/span&gt;
&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;tmp_path&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;given_path&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;../virtualenv&amp;#39;&lt;/span&gt;
&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;So we can check the effects of that function on the temp folder - more of an integration test than a unit test, but at least we're now testing behaviour rather than testing the implementation.  But it'll involve a lot of logic to correctly mock out the fabric API... &lt;code&gt;run&lt;/code&gt; may not be too hard, but I'm also using &lt;code&gt;append&lt;/code&gt; and &lt;code&gt;sed&lt;/code&gt;...  I'm worried I'll spend as much time debuggin test code as the real code!&lt;/p&gt;
&lt;h3&gt;3. Run the actual commands against a test VM&lt;/h3&gt;
&lt;p&gt;You could spin up some kind of lightweight VM / linux container (docker?) and let the fabric script run against that, checking the effects using fabric too.&lt;/p&gt;
&lt;p&gt;That might be kinda cool, but it definitely feels very heavyweight -- it's certainly more than I want to tell my readers to set up on their machines (what about Windows users!), and it ends up being a lot like what I'm doing with Selenium, running the acceptance tests against the staging server...&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;So what to do? One possibility would be to consign all this stuff to an appendix, in which I demonstrate all three approaches, and then tell the reader that it's up to them to choose which they like... But can I do better? And should I even bother?&lt;/p&gt;
&lt;p&gt;So folks, what do you think?  Is it worth writing unit tests for fabric scripts?  If so, how?&lt;/p&gt;</summary><category term="unit tests"></category><category term="fabric"></category><category term="deployment"></category><category term="mocking"></category></entry><entry><title>Fast tests are useless, hot lava be damned</title><link href="http://www.obeythetestinggoat.com/fast-tests-useless-hot-lava-be-damned.html" rel="alternate"></link><published>2013-09-11T04:00:00+01:00</published><updated>2013-09-11T04:00:00+01:00</updated><author><name>Harry</name></author><id>tag:www.obeythetestinggoat.com,2013-09-11:fast-tests-useless-hot-lava-be-damned.html</id><summary type="html">&lt;p&gt;tl;dr: I think there's a real danger that striving for an ultrafast test suite,
will lead you to overly mocky, disjointed tests which don't help you
catch bugs and don't help you code well.&lt;/p&gt;
&lt;p&gt;At this year's DjangoCon, Casey Kinsey gave a talk in which he (at least
ostensibly) advocated striving for faster test suites.  As we'll find out, I've
often thought that striving for fast tests suites can be a bit misguided, so I
went along preparing to disagree loudly -- as anyone who knows me can attest,
disagreeing loudly is just about my favourite thing to do, and a major
character flaw.&lt;/p&gt;
&lt;p&gt;As it turns out I found very little to disagree with, aside from &lt;a href="http://www.slideshare.net/cordiskinsey/djangocon-2013-how-to-write-fast-and-efficient-unit-tests-in-django/22"&gt;one
particularly tortured use of a Mock object&lt;/a&gt;
which definitely made baby test sanity Jesus cry.  It was an excellent talk, 
and full of good advice, as well as the memorable quote &lt;em&gt;"The Database is Hot
Lava"&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;But, is it?&lt;/p&gt;
&lt;h2&gt;The traditional argument: slow tests are bad&lt;/h2&gt;
&lt;p&gt;Here's the traditional argument for fast tests:&lt;/p&gt;
&lt;p&gt;Tests that take ages to run won't get run, which will break your development
process in several ways, and reduce the benefits you get from testing.  You
won't pick up defects early, because no-one is running the full test suite
before committing, and you won't be able to get the secondary benefit which 
you hope to gain from TDD, which is better designed code, because you can't
do TDD with slow tests.&lt;/p&gt;
&lt;p&gt;The reference for this point of view is Gary Bernhardt's talk from Pycon
2013, entitled &lt;a href="https://www.youtube.com/watch?v=RAxiiRPHS9k"&gt;Fast Tests, Slow test&lt;/a&gt;
If you haven't already, I &lt;strong&gt;strongly&lt;/strong&gt; encourage you to watch that talk, Gary 
knows what he's talking about, whereas I suspect I don't.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;update 2013-09-17&lt;/em&gt; -- everyone should also watch Gary's follow-up talk, 
&lt;a href="https://www.youtube.com/watch?v=eOYal8elnZk"&gt;Boundaries&lt;/a&gt; in which he 
expands on the concepts of test isolation, and how it fits with trying to adopt
a more functional paradigm in the core business logic of your application.&lt;/p&gt;
&lt;h2&gt;Why I think this is wrong: fast tests don't help you code, and they don't help you find bugs&lt;/h2&gt;
&lt;p&gt;I'm being deliberately provocative here, and this is a bit of a strawman, but
I think the problem with turning slow tests into fast tests is that you end
up with worse tests.  Why is that?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;faster tests mean more "unittey" tests -- tests with less dependencies&lt;/li&gt;
&lt;li&gt;that means using more mocks&lt;/li&gt;
&lt;li&gt;that leads to a very granular tests, which are highly decoupled from each
other&lt;/li&gt;
&lt;li&gt;and that, ultimately, doesn't help you find unexpected bugs&lt;/li&gt;
&lt;li&gt;and it doesn't help you find regressions when you're refactoring either&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So let's look at an example. Imagine our site does a bit of setup for each
user: it makes a temp folder for them, and then sets a flag on their user
profile:&lt;/p&gt;
&lt;p&gt;Imagine this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;setup_user_environment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;setup_temp_storage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;profile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_profile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environment_setup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Here's the kind of test I'm inclined to write:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_sets_up_temp_storage_and_sets_flag_on_profile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;jim&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;setup_user_environment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/tmp/jim&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_profile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environment_setup&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Arg!  That's not a unit test at all!  It touches the database, which is hot
lava! Worse still, it touches the filesystem!  It'll be really slow!  And it
has too many dependencies!&lt;/p&gt;
&lt;p&gt;That, at least, is what the purists would say.  They would prefer a "fast",
test that looks like this - a "real" unit test:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_calls_setup_temp_storage_and_sets_flag_on_profile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;mock_user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Mock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;myapp.setup_temp_storage&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;mock_setup_temp_storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;setup_user_environment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mock_user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;mock_setup_temp_storage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert_called_once_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mock_user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;mock_profile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mock_user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_profile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;return_value&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mock_profile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environment_setup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Well, I would argue that you have a much less readable test there, and it's
also a test that's very closely coupled to the implementation.  It discourages
refactoring, because something as simple as changing the name of the helper
method &lt;code&gt;setup_temp_storage&lt;/code&gt; involves changing the test code in 4 places --
three of which (eg &lt;code&gt;mock_setup_temp_storage&lt;/code&gt;) won't be found by automated
refactoring tools.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;IMO, there's no point in a test that duplicates the implementation, line
for line, with a series of mocks&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Imagine I change &lt;code&gt;setup_temp_storage&lt;/code&gt; to take a username instead of a user.
I go and find its unit tests and change it, then change its implementation.
What will happen next is that &lt;em&gt;my&lt;/em&gt; unit test for &lt;code&gt;setup_user_environment&lt;/code&gt; would
break, because it uses the real function, and so that's my reminder to change
the place it gets used. &lt;/p&gt;
&lt;p&gt;In contrast, in the "fast" test, &lt;code&gt;setup_user_environment&lt;/code&gt; is mocked, so that
test will still pass, even though my code is broken.&lt;/p&gt;
&lt;p&gt;Sure you could argue that my &lt;code&gt;os.path.exists&lt;/code&gt; call is tightly coupled to the
implementation of &lt;code&gt;setup_temp_storage&lt;/code&gt;, and that if I end up using it in lots
of tests, they'll be annoying to change, if I ever change the location of temp
storage, for example.  But I could factor it out into a test helper method, if
I notice myself duplicating test code a lot.&lt;/p&gt;
&lt;h2&gt;What is the correct balance of unit to integration tests?&lt;/h2&gt;
&lt;p&gt;Now unit test purists and I would probably agree that this example doesn't
prove you should &lt;em&gt;never&lt;/em&gt; mock anything, or that "proper" unit tests are
useless.  Clearly, both are useful, and as my example clearly shows, you
definitely need some level of integration tests to check that all your pieces
fit together.&lt;/p&gt;
&lt;p&gt;What I am saying is that, in a case where you can test a piece of code with
either a mocky or a non-mocky test, I prefer non-mocky tests.  Gary Bernhardt
says you should aim for 90% unit tests vs 10% "integration" tests (which is
what you'd call the kind of test I write), and I tend to think the ratio is
more like 50/50.&lt;/p&gt;
&lt;p&gt;Unit tests are definitely better for nailing down code which has lots of edge
cases and possible logical pitfalls -- like the classic example of a roman
numeral converter.  But my assertion is: in web development, that kind of code
is rare:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;helper methods on models tend to be quite simple&lt;/li&gt;
&lt;li&gt;view functions tend to be simple:  get a post request, instantiate a form,
  save a database item if the form validates and redirect, return a template
  with errors if not&lt;/li&gt;
&lt;li&gt;code tends to be hard to unit test without mocking -- the database, the
  Django request/response stack, or the template layer, or whatever.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I've just tended to find that there aren't many places where I find myself
spelling out more than 2 or 3 tests for any given function -- in which case, I
tend to find, unit tests don't offer any substantial advantage.  In the example
above, there's probably only one two cases, maybe a second one for the case
where &lt;code&gt;setup_temp_storage&lt;/code&gt; raises an exception.&lt;/p&gt;
&lt;p&gt;But what about the fact that integration tests are slow?  The database is hot
lava!  Isn't it?&lt;/p&gt;
&lt;h2&gt;The database is, at worst, lukewarm lava&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;NB - this is Django-specific&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Remember, Django uses an &lt;em&gt;in-memory&lt;/em&gt; Sqlite database when you're running its
unit tests. It's pretty fast.  Here's a test suite which hits the DB for some
tests and not for others, with 1,000 tests of each:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Car&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;colour&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_colour&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;colour&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;capitalize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;unittest&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.test&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TestCase&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;myapp.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Car&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FastTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unittest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_car&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Car&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;colour&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;blue&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SlowTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_car&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Car&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;colour&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;blue&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;method_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;test_car_{0}&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;testfn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;car&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_car&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;car&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_colour&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Blue&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;setattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FastTest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;testfn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;setattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SlowTest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;testfn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;So what's the difference in speed between the two?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ python manage.py &lt;span class="nb"&gt;test&lt;/span&gt; myapp.FastTest
Ran &lt;span class="m"&gt;1000&lt;/span&gt; tests in 0.108s

$ python manage.py &lt;span class="nb"&gt;test&lt;/span&gt; myapp.SlowTest
Ran &lt;span class="m"&gt;1000&lt;/span&gt; tests in 0.311s
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;A factor of 3.  YMMV. At most one order of magnitude, but certainly not two
orders of magnitude. And notice that's 1000 tests, still running in less than a
second. So, is shifting from tests that run in microseconds to tests that run
in tens of microseconds &lt;em&gt;really worth&lt;/em&gt; all the losses in terms of readability
and ease of refactoring?&lt;/p&gt;
&lt;h2&gt;Unsurprisingly, it's all down to your own circumstances&lt;/h2&gt;
&lt;p&gt;I think we all have a tendency to take the solutions we've applied to our own
particular circumanstances, and want to generalise them to universal rules,
saying they should apply to everybody.&lt;/p&gt;
&lt;p&gt;Casey's team didn't have a CI setup, so their only way of preventing
regressions was for the individual developer to run the full suite before
checking in code. Their test suite was taking 45 minutes to run, leading to
developers skipping the test run and checking in broken code. You might argue
that their real problem was a problem of process, but they couldn't fix that,
so instead they put effort into making their tests faster, and in the process
made them more efficient and better, so it was a win for them.&lt;/p&gt;
&lt;p&gt;At work, we have also have a unit test suite that takes 45 minutes to run (many
of the tests aren't very unittey, and are in fact very integrationey).  So
there's no way that we run all the unit tests as we do TDD (which we do for
everything).  Instead, we run a subset of the tests (usually the Django app
we're working on), and we leave the CI system to run the full unit test suite
overnight.&lt;/p&gt;
&lt;p&gt;And you know what? &lt;em&gt;The full unit test suite almost never picks up any bugs&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;That's because our code is well compartmentalised, and, even though they're not
very unit-ey unit tests, they are still quite granular and independent from
each other.&lt;/p&gt;
&lt;p&gt;Instead, we have a suite of 400-odd &lt;em&gt;functional&lt;/em&gt; tests that run with Selenium;
these are definitely integration/system tests, or what some people would 
call acceptance tests. They check every part of the application -- and they
&lt;em&gt;do&lt;/em&gt; find unexpected bugs. They take about 8 hours for a full run, so you bet
we only run one or two individual FTs during day-to-day TDD.&lt;/p&gt;
&lt;p&gt;Now, we're building a PaaS, so we have a lot of what Gary B. would call
"boundaries" - a lot of dependencies on external systems: the filesystem, the
database (we run a shared-hosting database-as-a-service too), Tornado +
websockets, Paypal, Dropbox, github, pypi, linux chroots and cgroups, CRON,
Nginx and uWSGI, and many more.  There's a lot of moving parts, and ultimately
the only thing that's going to reassure us that everything really works is a
full-stack test.&lt;/p&gt;
&lt;p&gt;So that's what works for us.  We are pretty much forced to have a lot of slow
tests, so maybe I'm just trying to justify our own specific circumstances and
try and force a generalisation onto the world.&lt;/p&gt;
&lt;h2&gt;My turn to generalise from my own circumstances!&lt;/h2&gt;
&lt;p&gt;But I'm not so sure.  I really think there's something to it.  I think you
really do lose a lot from using mocks everywhere, and I think that the price
you pay in terms of test speed is sometimes worth paying if you want more
"realistic" tests.&lt;/p&gt;
&lt;p&gt;I would say this:  don't optimise prematurely.  Start by writing tests in the
way that seems most obvious to you, tests that are as readable as possible, 
and don't couple themselves too tightly to the implementation with a lot of
mocks.  If test speed becomes a problem at some point down the road, there
are plenty of smart people out there that will give you tips on how to speed
them up -- just remember that mocky unit tests don't really test your
application, so you'll need to keep a few end-to-end tests in there no matter
what.&lt;/p&gt;
&lt;p&gt;Over to you folks!  I'm far from an expert, have only been writing tests for
about 3 years, and all for the same company.  Have you ever been bitten by
a bug because your tests were too mocky?  What ratio of unit tests to
integration tests works for you?&lt;/p&gt;</summary><category term="unit tests"></category><category term="speed"></category><category term="mocking"></category></entry><entry><title>TDD videos from EuroPython 2013, mine and others</title><link href="http://www.obeythetestinggoat.com/tdd-videos-from-europython-2013-mine-and-others.html" rel="alternate"></link><published>2013-07-03T10:43:00+01:00</published><updated>2013-07-03T10:43:00+01:00</updated><author><name>Harry</name></author><id>tag:www.obeythetestinggoat.com,2013-07-03:tdd-videos-from-europython-2013-mine-and-others.html</id><summary type="html">&lt;p&gt;I can't believe the videos are already up!  Here's a few links:&lt;/p&gt;
&lt;p&gt;My usual &lt;a href="https://www.youtube.com/watch?v=Pz-DIkpk7PM"&gt;TDD/Django Tutorial&lt;/a&gt; now featuring the example from the book!
People's favourite bit seemed to be when I stand on the chair...&lt;/p&gt;
&lt;iframe width="560" height="315" src="//www.youtube.com/embed/Pz-DIkpk7PM" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;

&lt;p&gt;Me in a faster &lt;a href="https://www.youtube.com/watch?v=u0L3DuAPOVw"&gt;TDD/Python talk in Italiano&lt;/a&gt;.  Still can't believe the organisers
put me on as the first Italian talk of the whole week. I think it was some kind of evil joke.&lt;/p&gt;
&lt;iframe width="560" height="315" src="//www.youtube.com/embed/u0L3DuAPOVw" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;

&lt;p&gt;Rob Collins' &lt;a href="https://www.youtube.com/watch?v=Nj4nwh_VrPM"&gt;Test-Driven-Madness &lt;/a&gt; which I couldn't see 
because it was on at the same time as my tutorial, but from what he told me about it while we were
sitting at the same desk doing prep around 1AM the previous day, it was pretty entertaining. I have
had independent confirmation of this too.&lt;/p&gt;
&lt;iframe width="560" height="315" src="//www.youtube.com/embed/Nj4nwh_VrPM" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=k6Z2JHUmZXM"&gt;Holger Krekel on pytest&lt;/a&gt; again, haven't seen this, but 
any man that can speak that passionately about the politics of P2P cannot possibly ever go wrong.&lt;/p&gt;
&lt;iframe width="560" height="315" src="//www.youtube.com/embed/k6Z2JHUmZXM" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;

&lt;p&gt;Great conference so far! Any more out there?&lt;/p&gt;</summary><category term="tdd"></category><category term="conference"></category><category term="video"></category></entry><entry><title>How to unit test tornado ioloop callbacks</title><link href="http://www.obeythetestinggoat.com/how-to-unit-test-tornado-ioloop-callbacks.html" rel="alternate"></link><published>2013-06-10T19:11:00+01:00</published><updated>2013-06-10T19:11:00+01:00</updated><author><name>Harry</name></author><id>tag:www.obeythetestinggoat.com,2013-06-10:how-to-unit-test-tornado-ioloop-callbacks.html</id><summary type="html">&lt;p&gt;&lt;em&gt;WARNING: this is not battle-tested wisdom of a massively experienced tornado
tester. Today was the first time we ever tried to test something that actually
uses the ioloop, and we've probably got it all totally backwards.  Still, in
case it helps...&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Async. It's always hard to wrap your head around, so perhaps it's not
surprising that it took us a few goes at &lt;a href="http://www.pythonanywhere.com"&gt;work&lt;/a&gt;
today before we got the hang of it.&lt;/p&gt;
&lt;p&gt;Here's a bit of code that adds a callback to the tornado ioloop:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sort_that_out&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mess&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;IOLoop&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;How might one naively write a test for it?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestSortingStuffOut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unittest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_stuff_get_sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;stuff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;sort_that_out&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mess&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mess&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Well, that doesn't work: &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;AssertionError&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Lists&lt;/span&gt; &lt;span class="n"&gt;differ&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;A little head-scratching will get you to the fact that it's because the tornado
IOLoop hasn't actually been started, so our callback never gets run.  So, let's fix that:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_stuff_get_sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;stuff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;sort_that_out&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stuff&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;IOLoop&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stuff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;What about now?  The test hangs, and a little Ctrl-C based profiling tells us where the
busy loop is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;  File &amp;quot;/tmp/t.py&amp;quot;, line 13, in test_stuff_get_sorted     
    IOLoop.instance().start()
  File &amp;quot;/usr/local/lib/python2.7/site-packages/tornado/ioloop.py&amp;quot;, line 627, in start
     event_pairs = self._impl.poll(poll_timeout) 
KeyboardInterrupt 
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Right.  &lt;code&gt;start()&lt;/code&gt; on the IOLoop is a blocking call, and just assumes the loop should 
be run forever.  At this point we ventured over to the &lt;a href="http://www.tornadoweb.org/en/stable/testing.html"&gt;official tornado
testing docs&lt;/a&gt; but they seem
to suggest a lot of overcomplicated things: using a &lt;code&gt;self.wait&lt;/code&gt;, overriding
&lt;code&gt;get_new_ioloop&lt;/code&gt; to return the singleton...&lt;/p&gt;
&lt;p&gt;Actually, all you really need to do is this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_stuff_get_sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;stuff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;sort_that_out&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stuff&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;IOLoop&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IOLoop&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;IOLoop&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stuff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We just add our own callback, telling the loop to shut itself down, making sure
that it's the last callback added before we start the loop.  Voila!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;.
 ---------------------------------------------------------------------- 
Ran 1 test in 0.001s

OK 
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Well, that was our first foray into writing a test for tornado that actually used the IOLoop 
(all our other tests have just mocked everything).  No doubt the tornado tools come in useful
for other use cases.  And you'd probably want to use a &lt;code&gt;tearDown&lt;/code&gt; or &lt;code&gt;addCleanup&lt;/code&gt; that made 
sure the IOLoop got shut down even when your test doesn't behave as expected....&lt;/p&gt;
&lt;p&gt;But I though I'd post this in case anybody else has a simple requirement to
test a tornado async callback, and finds the docs a little hard-going. Hope it helps!&lt;/p&gt;</summary><category term="tornado"></category><category term="async"></category></entry><entry><title>Using the built-in views and forms for new user registration in Django</title><link href="http://www.obeythetestinggoat.com/using-the-built-in-views-and-forms-for-new-user-registration-in-django.html" rel="alternate"></link><published>2013-06-09T01:10:00+01:00</published><updated>2013-06-09T01:10:00+01:00</updated><author><name>Harry</name></author><id>tag:www.obeythetestinggoat.com,2013-06-09:using-the-built-in-views-and-forms-for-new-user-registration-in-django.html</id><summary type="html">&lt;p&gt;Have been digging into the built-in forms and views from
django.contrib.auth.  I always knew you could get generic views for login,
logout, even password reset, but I didn't know you could actually handle
new user creation as well!&lt;/p&gt;
&lt;p&gt;There's a form in &lt;code&gt;django.contrib.auth.forms&lt;/code&gt;, and a class-based view for
creating new objects called &lt;code&gt;CreateView&lt;/code&gt;, and I believe this is all you
need for a working registration form/view:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.views.generic.edit&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CreateView&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.contrib.auth.forms&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;UserCreationForm&lt;/span&gt;

&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;patterns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;^register/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CreateView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;as_view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;template_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;register.html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;form_class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;UserCreationForm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;success_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;
    &lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;^accounts/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;django.contrib.auth.urls&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;

    &lt;span class="c1"&gt;# rest of your URLs as normal&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Then, in &lt;em&gt;register.html&lt;/em&gt; (which you do have to create yourself), you get a 
&lt;code&gt;{{ form }}&lt;/code&gt; you can use, including a username, password and password
confirmation, and it handles validation errors and EVERYTHING.&lt;/p&gt;
&lt;p&gt;Obviously it doesn't include registering (and validating) an email address 
for the user, but still, I'm pretty excited.  Did everyone else already
know about this?&lt;/p&gt;</summary><category term="django"></category><category term="authentication"></category></entry><entry><title>What to say about deployment?</title><link href="http://www.obeythetestinggoat.com/what-to-say-about-deployment.html" rel="alternate"></link><published>2013-05-15T07:37:00+01:00</published><updated>2013-05-15T07:37:00+01:00</updated><author><name>Harry</name></author><id>tag:www.obeythetestinggoat.com,2013-05-15:what-to-say-about-deployment.html</id><summary type="html">&lt;p&gt;My book has got to the stage of a minimum viable site.  I want the next chapter
to be about actually deploying the site, even though it's ridiculously early --
to encourage the habit of "deploy early, deploy often".&lt;/p&gt;
&lt;p&gt;But how to introduce deployment in a beginner-friendly way?  It's a very simple
site, so we don't need to cover all the complexities of deployment, but what's 
the minimum? Here's what I've got so far..&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Deploying a site to a live web server can be a tricky topic.  Oft heard in 
office corridors, IRC and tech forums is the forlorn cry -- &lt;em&gt;"but it works on my machine"&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Some of the danger areas of deployment include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;static files&lt;/strong&gt; (CSS, javascript, images etc): web servers usually need special
  configuration for serving these&lt;/li&gt;
&lt;li&gt;the &lt;strong&gt;database&lt;/strong&gt;: there can be permissions and path issues, and we need to be
  careful about preserving data between deploys&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;dependencies&lt;/strong&gt;: we need to make sure that the packages are software relies on
  are installed on the server, and have the correct versions&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But there are solutions to all of these.  In order:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Using a &lt;strong&gt;staging site&lt;/strong&gt;, on the same infrastructure as the production site, can
  help us test out our deployments and get things right before we go to the
  "real" site&lt;/li&gt;
&lt;li&gt;We can also &lt;strong&gt;run our functional tests against the staging site&lt;/strong&gt;. They could
  include some smoke tests that, eg, CSS is loaded correctly.&lt;/li&gt;
&lt;li&gt;We can write a special functional &lt;strong&gt;test that checks the deploy process&lt;/strong&gt;, for
  example making sure database data is preserved (later, we can talk about South &lt;/li&gt;
&lt;li&gt;data migrations...)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Virtualenvs&lt;/strong&gt; are a useful tool for managing packages and dependencies on a
  server that's not entirely under your own control&lt;/li&gt;
&lt;li&gt;And finally, automation, automation, automation.  By using an &lt;strong&gt;automated
  script&lt;/strong&gt; to deploy new versions, and by using the same script to deploy to
  staging and production, we can reassure ourselves that staging is as much
  like live as possible.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;So far, so good (at least, I think.  feel free to pick holes!)&lt;/p&gt;
&lt;p&gt;But the question is: now what?  What platform to choose to deploy to?  What
should be in my automated deploy script?&lt;/p&gt;
&lt;p&gt;Platform:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Obviousy I think &lt;a href="http://www.pythonanywhere.com"&gt;PythonAnywhere&lt;/a&gt; is the
  natural choice and the easiest and the best and stuff. But I would say that.
  So I can't say that&lt;/li&gt;
&lt;li&gt;Heroku / Dotcloud et al are an option, but they involve quite a lot of
  specific config.  The precise tools used to deploy are all different, and
  might change by the time the book comes out&lt;/li&gt;
&lt;li&gt;How about "a generic VPS"?  Assuming the user has SSH access, I could use
  fabric to deploy...  That would let people use AWS, Digital Ocean, Linode or
  whoever. But then I get into the mess of apache / nginx / uwsgi config... and
  the trend these days seems to be to try and let users avoid that sort of
  hassle... Also, I really want to make sure people &lt;em&gt;actually&lt;/em&gt; do this, and
  deploy their site, and I'd rather they were able to do it somewhere free...&lt;/li&gt;
&lt;li&gt;Can I make some instructions generic enough that they apply to all platforms?
  I'm not sure...&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, your suggestions much appreciated! How do you do your deploys?  Fabric?
FTP?  Git hooks?  Something else?  What do you think would work as a simple
solution for beginners?&lt;/p&gt;
&lt;p&gt;&lt;em&gt;[update 2013-06-09] I've had a first bash at it, and the solution involves the
reader starting up their own server, provisioning nginx + gunicorn + upstart,
and deploying using fabric, git and virtualenvs.  comments welcomed!  You
can find the &lt;a href="http://chimera.labs.oreilly.com/books/1234000000754/ch08.html"&gt;current draft here&lt;/a&gt;]&lt;/em&gt;&lt;/p&gt;</summary><category term="deploy"></category><category term="django"></category><category term="production"></category><category term="heroku"></category><category term="fabric"></category><category term="staging"></category></entry><entry><title>A lovely review! "like sitting down to pair-program with the author"</title><link href="http://www.obeythetestinggoat.com/review-like-sitting-down-to-pair-program-with-the-author.html" rel="alternate"></link><published>2013-05-13T08:35:00+01:00</published><updated>2013-05-13T08:35:00+01:00</updated><author><name>Harry</name></author><id>tag:www.obeythetestinggoat.com,2013-05-13:review-like-sitting-down-to-pair-program-with-the-author.html</id><summary type="html">&lt;p&gt;Jason, whom I met at PyCon this year, wrote a lovely review of my book &lt;a href="http://shop.oreilly.com/product/0636920029533.do"&gt;on the
O'Reily site&lt;/a&gt;. I'm very grateful because I think
he actually helped me to see some things I didn't realise myself about the book.  I'll reproduce
it here, for which I hope my publisher will forgive me:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Programming books often fall into a few, easy to define categories. There are beginner's books, advanced books, reference books, books on specific libraries/frameworks, cookbooks, etc. "Test-Driven Web Development with Python" is none of the above; it's best described as a conversation with a developer.&lt;/p&gt;
&lt;p&gt;Reading the book is like sitting down to pair-program with the author, starting from scratch and building a basic web app. Along the way you'll obviously use Django, unit tests, and Selenium but you'll also use Git and other tools.&lt;/p&gt;
&lt;p&gt;I find far too many programming books compartmentalize the material, each section is separate and abstract. Rather, this book's strength is in the broad use and application of these tools. By the end of this book you won't be a master with these tools but you will have used them enough to build a good foundation for starting your own projects and into the habit of test, code, refactor, commit.&lt;/p&gt;
&lt;p&gt;For example, if you are new to open source development you'll hear about writing unit tests or using Git and want to use them yourself. But what's the next step, read a the unit test documentation or ProGit cover to cover? I don't need to be an expert, I just need someone to show me enough to get up and running. At that point I'll have completed a project and have the confidence and skills to tackle problems on my own.&lt;/p&gt;
&lt;p&gt;Like the answer to a job interview question, the book's biggest strength is also it's biggest weakness. Writing a book that covers so many tools the author must, by necessity, make assumptions about what the reader knows, and that's very, very hard.&lt;/p&gt;
&lt;p&gt;I came to the book without ever touching Django before. It wasn't always obvious how things worked nor why were doing them, particularly later in the book where there's more magic going on. I had to sit back and blindly follow along. At one point I got hung up because I mis-typed my URL, omitting a a forward-slash ("/") at the beginning, and my tests kept failing but I couldn't figure out why.&lt;/p&gt;
&lt;p&gt;Do URLs start with a forward slash? As a beginner, I don't know, and it's not obvious because Django does a lot of stuff for you. The code in the book was right, but I mis-typed it. I think had I come to the table with more Django experience I wouldn't have made that mistake.&lt;/p&gt;
&lt;p&gt;So while that's a particularly nasty typo, which really frustrated me, I can't say that a beginner should go off and read a Django book before attempting this one. In fact, quite the opposite. Because I got a survey of how everything works, covering so many tools so quickly, I'm in a much better position to troubleshoot and solve the problem myself. For example, "we've been writing a lot of unit tests, how can I write a unit test to help debug this unexpected failure?"&lt;/p&gt;
&lt;p&gt;Lastly, as I mentioned, the book is written like a conversation with a fellow developer at your side. There are numerous jokes and cultural references (e.g. refactoring cat, testing goat, etc.). This really lightens the tone (who doesn't joke with a co-worker?) as well as introduce the reader into the culture of open source development so that when you are walking around PyCon and someone has a stuffed goat or makes a "baaah" goat noise you'll fit right in! &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Writing the book in the style of a pair-programming conversation came naturally, since it's what
I do every day.  It's also the way that I learnt pretty much everything I'm writing about, so
it's natural that I should use it as a teaching style. Where Jason really has a point is that my
book isn't enough -- it's not a reference book, so you won't be able to use it as a reference
book.  It won't teach you everything about Django, or even everything about unit testing -- on
several occasions in the book I say "you should go and read the Django documentation", or "you
should take a look at the other assertion methods from the unittest module", and I think some
readers have found that frustrating.  "Why should I go elsewhere to learn?"...&lt;/p&gt;
&lt;p&gt;So I think this is something I need to set expectations about better, perhaps in the intro.
"This isn't a reference book".&lt;/p&gt;
&lt;p&gt;Any thoughts? From current readers, do you agree with Jason?  Do you think the current style and
content of the book are working?&lt;/p&gt;</summary><category term="review"></category><category term="pair programming"></category><category term="goat"></category></entry><entry><title>Day against DRM and this book's copyright</title><link href="http://www.obeythetestinggoat.com/day-against-drm-copyright.html" rel="alternate"></link><published>2013-05-03T18:35:00+01:00</published><updated>2013-05-03T18:35:00+01:00</updated><author><name>Harry</name></author><id>tag:www.obeythetestinggoat.com,2013-05-03:day-against-drm-copyright.html</id><summary type="html">&lt;p&gt;Hooray for O'Reilly, who are giving away ebooks half-price in celebration of the FSF's "Day against DRM"!&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.kqzyfj.com/click-7089628-11364391" target="_top"&gt;
&lt;img src="http://www.ftjcfx.com/image-7089628-11364391" width="200" height="200" alt="Save 50% on all Ebooks and Videos to celebrate Day Against DRM" border="0"/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;When I was first speaking to publishers about this book, I was a bit nervous.
I'm very intersed in digital rights and copyright reform, and in fact I'm
a reasonably active member of Pirate Party UK (or I was until this little 
project sucked up all my spare time!)... Could I find a publishing deal that
would be compatible with my views?  One of my colleagues in the German Pirate
Party came in for a lot of criticism when she wrote a book and discovered her
publisher sending out DMCA notices to filesharing sites...&lt;/p&gt;
&lt;p&gt;I support a manifesto that asks for a 10-year limit on copyright, and for all
non-commercial filesharing to be legal.  Could I find a publishing deal that
was at all consistent with that?  The first publisher I spoke to was having
none of it...&lt;/p&gt;
&lt;p&gt;That's why I was so glad when O'Reilly said they had absolutely no problem
with me distributing the book under a CC license...  After a bit of haggling,
they even agree to a clause in the contract that would say that the book would
revert to a CC-0 Public Domain license after 10 years.  Even if you think
the policy of 10 years is too short for many domains, it's certainly enough
for commercial exploitation of a technical book.&lt;/p&gt;
&lt;p&gt;So, hooray for my beloved publisher, hooray for Creative Commons, and hooray 
for the FSF!&lt;/p&gt;</summary><category term="book"></category><category term="O'Reilly"></category><category term="copyright"></category></entry><entry><title>"Test Driven Development of Web Applications" -- the book exists, and is available on Early Release</title><link href="http://www.obeythetestinggoat.com/tdd-web-programming-book-on-oreilly-early-release.html" rel="alternate"></link><published>2013-03-26T18:35:00+00:00</published><updated>2013-03-26T18:35:00+00:00</updated><author><name>Harry</name></author><id>tag:www.obeythetestinggoat.com,2013-03-26:tdd-web-programming-book-on-oreilly-early-release.html</id><summary type="html">&lt;p&gt;&lt;img alt="Front cover of book" src="/static/images/new_goat_cover_lrg.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;I can't quite believe this is actually happening, but I have not only signed a contract with O'Reilly, but i have actually written the first four chapters, or at least a first draft of the first four chapters!&lt;/p&gt;
&lt;p&gt;For anyone new, the idea is to provide an introduction to TDD for web app development, for beginners -- whether it's people who've never done web development and Django before, or just people who've never done TDD.&lt;/p&gt;
&lt;p&gt;I'm releasing the book as early as possible to try and gather feedback, so please, please do get yourself a copy, and tell me what I'm doing right and wrong!  What would you change?  Am I pitching it at the right level?  Is the pace right or wrong? Should I spend more or less time on explaining the basics of Django, more or less time on the basics of TDD, more or less time with detailed examples?&lt;/p&gt;
&lt;p&gt;Here's the book on the O'Reilly Early Release program:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.kqzyfj.com/click-7079286-11260198?url=http%3A%2F%2Fshop.oreilly.com%2Fproduct%2F0636920029533.do%3Fcmp%3Daf-code-book-product_cj_9781449367794_%7BPID%7D&amp;cjsku=0636920029533" target="_top"&gt;
Test-Driven Development with Python&lt;/a&gt;&lt;img src="http://www.awltovhc.com/image-7079286-11260198" width="1" height="1" border="0"/&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Buying direct from O'Reilly is probably the best option, they have DRM-free versions you can download in all sorts of formats (MOBI/epub/pdf)...&lt;/p&gt;
&lt;p&gt;Alternatively, if you have an account on Safari, you can &lt;a href="http://my.safaribooksonline.com/9781449365141"&gt;get it there too&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Remember, this is an early draft, so expect the occasional rough edges... But I really, really want your feedback folks, so do let me know what you think -- here in the comments, in the comments on either site, or just by email to &lt;a href="mailto:hjwp2@cantab.net"&gt;hjwp2@cantab.net&lt;/a&gt;.  &lt;/p&gt;
&lt;p&gt;Thanks to everyone for your kind words of support, and thanks in advance for your help!&lt;/p&gt;
&lt;p&gt;[update] -- in case it's not clear:  if you buy this book now, either via O'Reilly or via Safari, you'll get updates from now on, as each new chapter is released, until the final version is released and you get the full copy.  Anyone that provides feedback is totes getting a credit in the final book!&lt;/p&gt;
&lt;p&gt;[update 2] -- &lt;em&gt;naturally&lt;/em&gt; I assume you're all more than keen to &lt;strong&gt;pay&lt;/strong&gt; for this book, but just as a matter of principle, there will be a Creative Commons, "try-before-you-buy", free version of the early drafts, released sometime in the near future.  The final version of the book will also have a Creative Commons (NC-ND) digital version released on publication.  And after 10 years, it will automatically revert to CC-0 Public Domain. But I (and my publishers) very much appreciate every single early sale!&lt;/p&gt;
&lt;p&gt;[update 3] -- follow me on twitter &lt;a href="https://twitter.com/hjwp"&gt;@hjwp&lt;/a&gt; or sign up for this blog's updates to find out more about progress with the book, including when the CC version comes out... And, just let me know you're interested!&lt;/p&gt;</summary><category term="book"></category><category term="O'Reilly"></category><category term="Early Release"></category></entry><entry><title>"TDD / Django tutorial: The Book!"</title><link href="http://www.obeythetestinggoat.com/writing-a-book-preview.html" rel="alternate"></link><published>2013-03-03T00:00:00+00:00</published><updated>2013-03-03T00:00:00+00:00</updated><author><name>Harry</name></author><id>tag:www.obeythetestinggoat.com,2013-03-03:writing-a-book-preview.html</id><summary type="html">&lt;p&gt;&lt;em&gt;[update April 2012] : The book is now coming out on O'Reilly!  more info here:&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://www.tdd-django-tutorial.com/blog/articles/2013/test-driven-development-web-applications-book-exis/"&gt;http://www.tdd-django-tutorial.com/blog/articles/2013/test-driven-development-web-applications-book-exis/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Thanks so much for following on this far! I'm afraid that's all there is for
now, but I am about to start on the next stage -- a proper book on TDD for
web apps.&lt;/p&gt;
&lt;p&gt;If you've found the tutorial useful so far, I wonder whether I can solicit
some feeback regarding a book version?&lt;/p&gt;
&lt;p&gt;Here's the chapter outline I've got so far.  Now, remember, this is a very rough draft,
and it's very much bound to change, but I'd love to hear your thoughts... Especially
about some quite general questions, like:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Am I broadly covering the right sort of stuff?&lt;/li&gt;
&lt;li&gt;Am I right to spend plenty of time talking about JavaScript, as well as Python?&lt;/li&gt;
&lt;li&gt;What should I choose as my example app?  I've written the outline as if it
were a forums app, but I'm starting to see the appeal of one of the other classic
examples, the &amp;quot;to-do list&amp;quot; (very simple at base, can be extended easily, opportunities
for sharing/social bits, plenty of stuff to do on the client-side too...).  What
would you choose as an example?&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="section" id="part-1-beginning-web-app-development-with-tdd"&gt;
&lt;h2&gt;PART 1 - Beginning web app development with TDD&lt;/h2&gt;
&lt;p&gt;The idea is to dive straight in with a practical example, rather than talking
a lot of theory up-front.   I had originally thought to start with a toy example
(cf these 3 abandoned chapters: &lt;a class="reference external" href="http://www.tdd-django-tutorial.com/tutorial/6/"&gt;http://www.tdd-django-tutorial.com/tutorial/6/&lt;/a&gt; ),
but I decided that people prefer real practical stuff to toy examples...&lt;/p&gt;
&lt;p&gt;I also want the first few chapters to be very short + bit-sized, so that the
reader feels like they're really making progress quickly... (the inspiration
comes from Kent Beck's TDD by Example, an awesome book)&lt;/p&gt;
&lt;p&gt;Essentially part 1 is a re-hash of this online tutorial, using a differnt example
app...&lt;/p&gt;
&lt;div class="section" id="our-first-functional-test-with-selenium"&gt;
&lt;h3&gt;1: Our first functional test with Selenium&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Briefly discuss difference between functional testing (AKA acceptance
testing, integration testing, whatever) and unit testing&lt;/li&gt;
&lt;li&gt;Write first test - Introduce Selenium, &lt;cite&gt;setUp&lt;/cite&gt;, &lt;cite&gt;tearDown&lt;/cite&gt;&lt;/li&gt;
&lt;li&gt;Demonstrate we can get it to open a web browser, and navigate to a web page
eg - google.com&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="getting-django-set-up-and-running"&gt;
&lt;h3&gt;2: Getting Django set-up and running&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Change our test to look for the test server&lt;/li&gt;
&lt;li&gt;Switch to Django LiveServerTestCase. Explain&lt;/li&gt;
&lt;li&gt;Get the first test running and failing for a sensible reason&lt;/li&gt;
&lt;li&gt;Create django project &lt;cite&gt;django-admin.py startproject&lt;/cite&gt;&lt;/li&gt;
&lt;li&gt;It worked!&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="a-static-front-page"&gt;
&lt;h3&gt;3: A static front page&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Look for &amp;quot;Welcome to the Forums&amp;quot;, or similar&lt;/li&gt;
&lt;li&gt;&lt;cite&gt;urls.py&lt;/cite&gt;, &lt;cite&gt;direct_to_template&lt;/cite&gt; ?&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="super-users-and-the-django-admin-site"&gt;
&lt;h3&gt;4: Super-users and the Django admin site&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Extend FT to try and log in&lt;/li&gt;
&lt;li&gt;Explain the admin site&lt;/li&gt;
&lt;li&gt;Database setup, &lt;cite&gt;settings.py&lt;/cite&gt;, &lt;cite&gt;syncdb&lt;/cite&gt;, &lt;cite&gt;admin.py&lt;/cite&gt;&lt;/li&gt;
&lt;li&gt;&lt;cite&gt;runserver&lt;/cite&gt; to show login code&lt;/li&gt;
&lt;li&gt;Explain difference between test database and real database&lt;/li&gt;
&lt;li&gt;Fixtures&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="first-unit-tests-and-database-model"&gt;
&lt;h3&gt;5: First unit tests and Database model&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Distinction between unit tests and functional tests&lt;/li&gt;
&lt;li&gt;Extend FT to try and create a new topic&lt;/li&gt;
&lt;li&gt;new app&lt;/li&gt;
&lt;li&gt;&lt;cite&gt;models.py&lt;/cite&gt;&lt;/li&gt;
&lt;li&gt;test/code cycle&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="testing-a-view"&gt;
&lt;h3&gt;6: Testing a view&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;urls.py again&lt;/li&gt;
&lt;li&gt;Test view as a function&lt;/li&gt;
&lt;li&gt;assert on string contents&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="django-s-template-system"&gt;
&lt;h3&gt;7: Django's template system&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Introduce template syntax&lt;/li&gt;
&lt;li&gt;Keep testing as a function&lt;/li&gt;
&lt;li&gt;The, introduce the Django Test Client&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="reflections-what-to-test-what-not-to-test"&gt;
&lt;h3&gt;8: Reflections: what to test, what not to test&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;time for a bit of theory/philosophy&lt;/li&gt;
&lt;li&gt;&amp;quot;Don't test constants&amp;quot;&lt;/li&gt;
&lt;li&gt;Test logic&lt;/li&gt;
&lt;li&gt;Tests for simple stuff should be simple, so not much effort&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="simple-forms"&gt;
&lt;h3&gt;9: Simple Forms&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Manually coded HTML&lt;/li&gt;
&lt;li&gt;Refactor test classes&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="user-authentication"&gt;
&lt;h3&gt;10: User Authentication&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Sign up, login/logout&lt;/li&gt;
&lt;li&gt;Email?&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="more-advanced-forms"&gt;
&lt;h3&gt;11: More advanced forms&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Use Django Forms classes&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="on-refactoring"&gt;
&lt;h3&gt;12: On Refactoring&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Martin Fowler&lt;/li&gt;
&lt;li&gt;Tests critical&lt;/li&gt;
&lt;li&gt;Methodical process - explain step by step&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="pagination"&gt;
&lt;h3&gt;13: Pagination&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Extend various old unit tests and FTs&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="part-2-more-advanced-testing-for-a-more-advanced-site"&gt;
&lt;h2&gt;PART 2: More advanced testing for a more advanced site&lt;/h2&gt;
&lt;div class="section" id="notifications"&gt;
&lt;h3&gt;14: Notifications&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Django Notifications, for post edits&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="adding-style-with-markdown"&gt;
&lt;h3&gt;15: Adding style with MarkDown&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Using an external library&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="switching-to-oauth-mocking"&gt;
&lt;h3&gt;16: Switching to OAuth: Mocking&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&amp;quot;Don't store passwords&amp;quot;&lt;/li&gt;
&lt;li&gt;Discuss challenges of external dependencies&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="getting-dynamic-testing-javascript-part-1"&gt;
&lt;h3&gt;17: Getting Dynamic: Testing Javascript part 1&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Simple input validation&lt;/li&gt;
&lt;li&gt;Choose JS unit testing framework (probably Qunit, or YUI)&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="testing-javascript-part-2-ajax"&gt;
&lt;h3&gt;18: Testing Javascript part 2 - Ajax&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Dynamic previews of post input&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="getting-pretty-bootstrap"&gt;
&lt;h3&gt;19: Getting pretty: Bootstrap&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Bring in nicer UI elements&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="getting-pretty-gravatar"&gt;
&lt;h3&gt;20: Getting pretty: Gravatar&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;pictures for users&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="part-3-getting-seriously-sexy"&gt;
&lt;h2&gt;PART 3: Getting seriously sexy&lt;/h2&gt;
&lt;div class="section" id="getting-serious-about-the-client-side-single-page-website"&gt;
&lt;h3&gt;21: Getting serious about the client-side + single-page website?&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Introduce one of the client-side js frameworks -- backbone.js / ember.js / angular&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="switching-databases-1-postgresql"&gt;
&lt;h3&gt;22: Switching Databases 1: PostgreSQL&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;show how Django makes this easy&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="websockets-and-async-on-the-server-side"&gt;
&lt;h3&gt;23: Websockets and Async on the server-side&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;we want dynamic notifications of when new posts appear on a thread we're
looking at&lt;/li&gt;
&lt;li&gt;Need to spin up, Tornado/Twisted/Gevent as well as Django LiveServerTestCase&lt;/li&gt;
&lt;li&gt;FT opens multiple browser tabs in parallel&lt;/li&gt;
&lt;li&gt;Big change!&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="switching-databases-2-nosql-and-mongodb"&gt;
&lt;h3&gt;24: Switching Databases 2: NoSQL and MongoDB&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;obligatory discussion of NoSQL and MongoDB&lt;/li&gt;
&lt;li&gt;descrine installation, particularities of testing&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="continuous-integration"&gt;
&lt;h3&gt;26: Continuous Integration&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Need to build 3 server types&lt;/li&gt;
&lt;li&gt;Jenkins (or maybe buildbot)&lt;/li&gt;
&lt;li&gt;Need to adapt Fts, maybe rely less on LiveServerTestCase&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="caching-for-screamingly-fast-performance"&gt;
&lt;h3&gt;27: Caching for screamingly fast performance&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;unit testing &lt;cite&gt;memcached&lt;/cite&gt;&lt;/li&gt;
&lt;li&gt;Functionally testing performance&lt;/li&gt;
&lt;li&gt;Apache &lt;cite&gt;ab&lt;/cite&gt; testing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Well, that's what I have so far.  What do you think?  Have I missed anything
out?  Does anything seem superfluous?  Most importantly, would you buy it?&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
</summary><category term="tdd"></category><category term="book"></category></entry><entry><title>The next stage: writing a book</title><link href="http://www.obeythetestinggoat.com/writing-a-book-announcement.html" rel="alternate"></link><published>2013-01-08T00:00:00+00:00</published><updated>2013-01-08T00:00:00+00:00</updated><author><name>Harry</name></author><id>tag:www.obeythetestinggoat.com,2013-01-08:writing-a-book-announcement.html</id><summary type="html">&lt;p&gt;I've decided to try and develop this tutorial into an actual book on test-driven development for web apps.  Have a look at the chapter outline I've posted as part 6:&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://www.tdd-django-tutorial.com/tutorial/6/"&gt;http://www.tdd-django-tutorial.com/tutorial/6/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I'd love to hear what you think!  Am I completely mad?&lt;/p&gt;
</summary><category term="tdd"></category><category term="book"></category></entry></feed>