|
<?xml version="1.0" encoding="utf-8" ?> |
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> |
|
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> |
|
<head> |
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> |
|
<meta name="generator" content="Docutils 0.7: http://docutils.sourceforge.net/" /> |
|
<title>An Example of Test Driven Development</title> |
|
<link rel="stylesheet" href="style.css" type="text/css" /> |
|
</head> |
|
<body> |
|
<div class="document" id="an-example-of-test-driven-development"> |
|
<h1 class="title">An Example of Test Driven Development</h1> |
|
|
|
<p><a class="reference external" href="index.html">Back to index</a></p> |
|
<div class="contents topic" id="contents"> |
|
<p class="topic-title first">Contents</p> |
|
<ul class="simple"> |
|
<li><a class="reference internal" href="#steps" id="id1">Steps</a></li> |
|
<li><a class="reference internal" href="#doughnut" id="id2">Doughnut</a><ul> |
|
<li><a class="reference internal" href="#red" id="id3">Red</a></li> |
|
<li><a class="reference internal" href="#green" id="id4">Green</a></li> |
|
<li><a class="reference internal" href="#adding-features" id="id5">Adding features</a></li> |
|
<li><a class="reference internal" href="#general-case-then-exceptions" id="id6">General case then exceptions</a></li> |
|
<li><a class="reference internal" href="#refactor" id="id7">Refactor</a></li> |
|
</ul> |
|
</li> |
|
<li><a class="reference internal" href="#make-the-least-change-possible" id="id8">Make the least change possible</a></li> |
|
<li><a class="reference internal" href="#test-writing-test-driving" id="id9">Test writing != test driving</a></li> |
|
<li><a class="reference internal" href="#coverage" id="id10">Coverage</a></li> |
|
<li><a class="reference internal" href="#what-other-people-say" id="id11">What other people say</a></li> |
|
</ul> |
|
</div> |
|
<p>Test Driven Development (TDD) is a method of code development. You lead with |
|
tests, and follow with code.</p> |
|
<div class="section" id="steps"> |
|
<h1>Steps</h1> |
|
<ol class="arabic simple"> |
|
<li>Red - write a failing test</li> |
|
<li>Green - make the test pass</li> |
|
<li>Refactor - make it DRY instead of WET; optimize; document</li> |
|
</ol> |
|
</div> |
|
<div class="section" id="doughnut"> |
|
<h1>Doughnut</h1> |
|
<p>In the following example, we'll test-drive the production of a Doughnut. There |
|
are different kinds of doughnuts, but most are glazed.</p> |
|
<div class="section" id="red"> |
|
<h2>Red</h2> |
|
<p>The first step is to write a failing test for a <tt class="docutils literal">Doughtnut</tt> class that |
|
is 'glazed'.</p> |
|
<div class="highlight"><pre><span class="c"># test_doughnut.py</span> |
|
<span class="kn">from</span> <span class="nn">unittest</span> <span class="kn">import</span> <span class="n">TestCase</span> |
|
|
|
<span class="kn">from</span> <span class="nn">flour</span> <span class="kn">import</span> <span class="n">Doughnut</span> |
|
|
|
<span class="k">class</span> <span class="nc">DoughnutTest</span><span class="p">(</span><span class="n">TestCase</span><span class="p">):</span> |
|
|
|
<span class="k">def</span> <span class="nf">test_glazed</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> |
|
<span class="sd">"""</span> |
|
<span class="sd"> Doughtnuts are glazed by default</span> |
|
<span class="sd"> """</span> |
|
<span class="n">d</span> <span class="o">=</span> <span class="n">Doughnut</span><span class="p">()</span> |
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">d</span><span class="o">.</span><span class="n">kind</span><span class="p">,</span> <span class="s">'glazed'</span><span class="p">)</span> |
|
</pre></div> |
|
<p>Run the test to see it fail. I like using Twisted's <tt class="docutils literal">trial</tt> to run the tests.</p> |
|
<pre class="literal-block"> |
|
$ trial test_doughnut |
|
test_doughnut ... [ERROR] |
|
|
|
=============================================================================== |
|
[ERROR] |
|
Traceback (most recent call last): |
|
... |
|
File "/Volumes/FatMan/home/matt/tmp/dnut/test_doughnut.py", line 4, in <module> |
|
from flour import Doughnut |
|
exceptions.ImportError: No module named flour |
|
|
|
test_doughnut |
|
------------------------------------------------------------------------------- |
|
Ran 1 tests in 0.026s |
|
|
|
FAILED (errors=1) |
|
</pre> |
|
<p>Look at the last line:</p> |
|
<pre class="literal-block"> |
|
exceptions.ImportError: No module named flour |
|
</pre> |
|
<p>The test fails because there is no module named flour. You might be tempted to |
|
make a class named <tt class="docutils literal">Doughnut</tt> with an attribute <tt class="docutils literal">kind</tt> set to 'glazed' in a |
|
file named <tt class="docutils literal">flour.py</tt> -- <strong>DON'T!</strong> Resolve only the error at hand by |
|
creating a file named <tt class="docutils literal">flour.py</tt>.</p> |
|
<div class="highlight"><pre><span class="c"># flour.py</span> |
|
</pre></div> |
|
<p>Run the tests again to see that we solved the "No module named flour" error:</p> |
|
<pre class="literal-block"> |
|
=============================================================================== |
|
[ERROR] |
|
Traceback (most recent call last): |
|
... |
|
File "/Volumes/FatMan/home/matt/tmp/dnut/test_doughnut.py", line 4, in <module> |
|
from flour import Doughnut |
|
exceptions.ImportError: cannot import name Doughnut |
|
|
|
test_doughnut |
|
------------------------------------------------------------------------------- |
|
</pre> |
|
<p>It complains that we there's nothing named <tt class="docutils literal">Doughnut</tt>. Let's make a |
|
<tt class="docutils literal">Doughnut</tt> class:</p> |
|
<div class="highlight"><pre><span class="c"># flour.py</span> |
|
|
|
<span class="k">class</span> <span class="nc">Doughnut</span><span class="p">:</span> |
|
<span class="sd">"""</span> |
|
<span class="sd"> I am a doughnut</span> |
|
<span class="sd"> """</span> |
|
</pre></div> |
|
<p>On to the next failure:</p> |
|
<pre class="literal-block"> |
|
[ERROR] |
|
Traceback (most recent call last): |
|
... |
|
File "/Volumes/FatMan/home/matt/tmp/dnut/test_doughnut.py", line 13, in test_glazed |
|
self.assertEqual(d.kind, 'glazed') |
|
exceptions.AttributeError: Doughnut instance has no attribute 'kind' |
|
|
|
test_doughnut.DoughnutTest.test_glazed |
|
</pre> |
|
<p>To make the <tt class="docutils literal">AttributeError</tt> go away add a <tt class="docutils literal">kind</tt> attribute to <tt class="docutils literal">Doughnut</tt>:</p> |
|
<div class="highlight"><pre><span class="c"># flour.py</span> |
|
|
|
<span class="k">class</span> <span class="nc">Doughnut</span><span class="p">:</span> |
|
<span class="sd">"""</span> |
|
<span class="sd"> I am a doughnut</span> |
|
<span class="sd"> """</span> |
|
|
|
<span class="n">kind</span> <span class="o">=</span> <span class="bp">None</span> |
|
</pre></div> |
|
<pre class="literal-block"> |
|
[FAIL] |
|
Traceback (most recent call last): |
|
... |
|
File "/Volumes/FatMan/home/matt/tmp/dnut/test_doughnut.py", line 13, in test_glazed |
|
self.assertEqual(d.kind, 'glazed') |
|
exceptions.AssertionError: None != 'glazed' |
|
|
|
test_doughnut.DoughnutTest.test_glazed |
|
</pre> |
|
<p>It wants a glazed doughnut. Glaze the <tt class="docutils literal">Doughnut</tt>:</p> |
|
<div class="highlight"><pre><span class="c"># flour.py</span> |
|
|
|
<span class="k">class</span> <span class="nc">Doughnut</span><span class="p">:</span> |
|
<span class="sd">"""</span> |
|
<span class="sd"> I am a doughnut</span> |
|
<span class="sd"> """</span> |
|
|
|
<span class="n">kind</span> <span class="o">=</span> <span class="s">'glazed'</span> |
|
</pre></div> |
|
<pre class="literal-block"> |
|
test_doughnut |
|
DoughnutTest |
|
test_glazed ... [OK] |
|
|
|
------------------------------------------------------------------------------- |
|
Ran 1 tests in 0.026s |
|
|
|
PASSED (successes=1) |
|
</pre> |
|
</div> |
|
<div class="section" id="green"> |
|
<h2>Green</h2> |
|
<p>Our first test passed! Now we would refactor, except this code is so simple |
|
there's nothing to refactor. I usually update the documentation in this step.</p> |
|
<div class="highlight"><pre><span class="c"># flour.py</span> |
|
|
|
<span class="k">class</span> <span class="nc">Doughnut</span><span class="p">:</span> |
|
<span class="sd">"""</span> |
|
<span class="sd"> I am a doughnut.</span> |
|
|
|
<span class="sd"> @type kind: C{str}</span> |
|
<span class="sd"> @ivar kind: The kind of doughnut this is.</span> |
|
<span class="sd"> """</span> |
|
|
|
<span class="n">kind</span> <span class="o">=</span> <span class="s">'glazed'</span> |
|
</pre></div> |
|
</div> |
|
<div class="section" id="adding-features"> |
|
<h2>Adding features</h2> |
|
<p>Some doughnuts are better than others: new doughnuts are better than old ones; |
|
maple bars are better than glazed; etc... Let's enhance our <tt class="docutils literal">Doughnut</tt> by |
|
providing a method for testing tastiness. This will involve writing |
|
several more tests. I've found it helpful to write the most common case first |
|
followed by a test for every exception to the common case. Glazed doughtnuts |
|
are tasty by default:</p> |
|
<div class="highlight"><pre><span class="c"># test_doughnut.py snippet</span> |
|
|
|
<span class="k">def</span> <span class="nf">test_tasty</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> |
|
<span class="sd">"""</span> |
|
<span class="sd"> Doughtnuts are tasty by default</span> |
|
<span class="sd"> """</span> |
|
<span class="n">d</span> <span class="o">=</span> <span class="n">Doughnut</span><span class="p">()</span> |
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">d</span><span class="o">.</span><span class="n">tasty</span><span class="p">(),</span> <span class="bp">True</span><span class="p">)</span> |
|
</pre></div> |
|
<p>Running the tests fails with an <tt class="docutils literal">AttributeError</tt>:</p> |
|
<pre class="literal-block"> |
|
exceptions.AttributeError: Doughnut instance has no attribute 'tasty' |
|
</pre> |
|
<p>For brevity, I won't show all the steps to get this test to pass. Here's the |
|
passing, documented <tt class="docutils literal">flour.py</tt>:</p> |
|
<div class="highlight"><pre><span class="c"># flour.py snippet</span> |
|
|
|
<span class="k">def</span> <span class="nf">tasty</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> |
|
<span class="sd">"""</span> |
|
<span class="sd"> Tests doughnut tastiness.</span> |
|
|
|
<span class="sd"> @rtype: C{bool}</span> |
|
<span class="sd"> @return: C{True} if this doughnut is tasty.</span> |
|
<span class="sd"> """</span> |
|
<span class="k">return</span> <span class="bp">True</span> |
|
</pre></div> |
|
<p>Various factors contribute to or detract from a doughnut's tastiness including |
|
the age of the doughnut, the location of the doughnut and the cost of the |
|
doughnut. Let's give the <tt class="docutils literal">Doughnut</tt> some attributes so that we can |
|
later change <tt class="docutils literal">tasty</tt> to consider them. Do this by modifying and |
|
renaming the first test <tt class="docutils literal">test_glazed</tt> to <tt class="docutils literal">test_default</tt>:</p> |
|
<div class="highlight"><pre><span class="c"># test_doughnut.py snippet</span> |
|
|
|
<span class="k">def</span> <span class="nf">test_default</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> |
|
<span class="sd">"""</span> |
|
<span class="sd"> Doughtnuts are glazed, new, not free and in a box.</span> |
|
<span class="sd"> """</span> |
|
<span class="n">d</span> <span class="o">=</span> <span class="n">Doughnut</span><span class="p">()</span> |
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">d</span><span class="o">.</span><span class="n">kind</span><span class="p">,</span> <span class="s">'glazed'</span><span class="p">)</span> |
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">d</span><span class="o">.</span><span class="n">age</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> |
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">d</span><span class="o">.</span><span class="n">free</span><span class="p">,</span> <span class="bp">False</span><span class="p">)</span> |
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">d</span><span class="o">.</span><span class="n">location</span><span class="p">,</span> <span class="s">'box'</span><span class="p">)</span> |
|
</pre></div> |
|
<p>The tests fail until, line by line, we've change <tt class="docutils literal">flour.py</tt> into this (I've |
|
removed the docstrings for brevity):</p> |
|
<div class="highlight"><pre><span class="c"># flour.py</span> |
|
|
|
<span class="k">class</span> <span class="nc">Doughnut</span><span class="p">:</span> |
|
|
|
<span class="n">kind</span> <span class="o">=</span> <span class="s">'glazed'</span> |
|
<span class="n">age</span> <span class="o">=</span> <span class="mi">0</span> |
|
<span class="n">free</span> <span class="o">=</span> <span class="bp">False</span> |
|
<span class="n">location</span> <span class="o">=</span> <span class="s">'box'</span> |
|
|
|
<span class="k">def</span> <span class="nf">tasty</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> |
|
<span class="k">return</span> <span class="bp">True</span> |
|
</pre></div> |
|
</div> |
|
<div class="section" id="general-case-then-exceptions"> |
|
<h2>General case then exceptions</h2> |
|
<p>Let's now change <tt class="docutils literal">tasty()</tt> to use these new attributes in the following way:</p> |
|
<ol class="arabic simple"> |
|
<li>Most doughnuts older than 2 days are not tasty</li> |
|
<li>Maple bars are tasty for 5 days</li> |
|
<li>Doughnuts found in dumpsters are not tasty if you have to pay for them.</li> |
|
<li>Free doughnuts in dumpsters are very tasty</li> |
|
<li>Apple fritters are questionable</li> |
|
</ol> |
|
<p>Notice that requirement 2 is an exception to 1, which is the more general case. |
|
So let's write a test for 1 first, make it pass, then add a test for 2.</p> |
|
<p>Test and passing code to implement requirement 1:</p> |
|
<div class="highlight"><pre><span class="c"># test_doughnut.py snippet</span> |
|
|
|
<span class="k">def</span> <span class="nf">test_old_doughnuts</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> |
|
<span class="n">d</span> <span class="o">=</span> <span class="n">Doughnut</span><span class="p">()</span> |
|
<span class="n">d</span><span class="o">.</span><span class="n">age</span> <span class="o">=</span> <span class="mi">3</span> |
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">d</span><span class="o">.</span><span class="n">tasty</span><span class="p">(),</span> <span class="bp">False</span><span class="p">)</span> |
|
</pre></div> |
|
<div class="highlight"><pre><span class="c"># flour.py snippet</span> |
|
|
|
<span class="k">def</span> <span class="nf">tasty</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> |
|
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">age</span> <span class="o">></span> <span class="mi">2</span><span class="p">:</span> |
|
<span class="k">return</span> <span class="bp">False</span> |
|
<span class="k">return</span> <span class="bp">True</span> |
|
</pre></div> |
|
<p>Then the case for maple bars (requirement 2):</p> |
|
<div class="highlight"><pre><span class="c"># test_doughnut.py snippet</span> |
|
|
|
<span class="k">def</span> <span class="nf">test_old_maplebars</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> |
|
<span class="n">d</span> <span class="o">=</span> <span class="n">Doughnut</span><span class="p">()</span> |
|
<span class="n">d</span><span class="o">.</span><span class="n">kind</span> <span class="o">=</span> <span class="s">'maple'</span> |
|
<span class="n">d</span><span class="o">.</span><span class="n">age</span> <span class="o">=</span> <span class="mi">3</span> |
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">d</span><span class="o">.</span><span class="n">tasty</span><span class="p">(),</span> <span class="bp">True</span><span class="p">)</span> |
|
|
|
<span class="n">d</span><span class="o">.</span><span class="n">age</span> <span class="o">=</span> <span class="mi">6</span> |
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">d</span><span class="o">.</span><span class="n">tasty</span><span class="p">(),</span> <span class="bp">False</span><span class="p">)</span> |
|
</pre></div> |
|
<div class="highlight"><pre><span class="c"># flour.py snippet</span> |
|
|
|
<span class="k">def</span> <span class="nf">tasty</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> |
|
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">age</span> <span class="o">></span> <span class="mi">2</span><span class="p">:</span> |
|
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">kind</span> <span class="o">==</span> <span class="s">'maple'</span><span class="p">:</span> |
|
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">age</span> <span class="o">></span> <span class="mi">5</span><span class="p">:</span> |
|
<span class="k">return</span> <span class="bp">False</span> |
|
<span class="k">return</span> <span class="bp">True</span> |
|
<span class="k">return</span> <span class="bp">False</span> |
|
<span class="k">return</span> <span class="bp">True</span> |
|
</pre></div> |
|
</div> |
|
<div class="section" id="refactor"> |
|
<h2>Refactor</h2> |
|
<p>Ugly code! Let's refactor the tests by removing duplicate code first. |
|
We can reuse the concept of an old doughnut:</p> |
|
<div class="highlight"><pre><span class="c"># test_doughnut.py snippet</span> |
|
|
|
<span class="k">def</span> <span class="nf">getOld</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> |
|
<span class="n">d</span> <span class="o">=</span> <span class="n">Doughnut</span><span class="p">()</span> |
|
<span class="n">d</span><span class="o">.</span><span class="n">age</span> <span class="o">=</span> <span class="mi">3</span> |
|
<span class="k">return</span> <span class="n">d</span> |
|
|
|
<span class="k">def</span> <span class="nf">test_old_doughnuts</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> |
|
<span class="n">d</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">getOld</span><span class="p">()</span> |
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">d</span><span class="o">.</span><span class="n">tasty</span><span class="p">(),</span> <span class="bp">False</span><span class="p">)</span> |
|
|
|
<span class="k">def</span> <span class="nf">test_old_maplebars</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> |
|
<span class="n">d</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">getOld</span><span class="p">()</span> |
|
<span class="n">d</span><span class="o">.</span><span class="n">kind</span> <span class="o">=</span> <span class="s">'maple'</span> |
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">d</span><span class="o">.</span><span class="n">tasty</span><span class="p">(),</span> <span class="bp">True</span><span class="p">)</span> |
|
|
|
<span class="n">d</span><span class="o">.</span><span class="n">age</span> <span class="o">=</span> <span class="mi">6</span> |
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">d</span><span class="o">.</span><span class="n">tasty</span><span class="p">(),</span> <span class="bp">False</span><span class="p">)</span> |
|
</pre></div> |
|
<p>Make sure the tests still pass:</p> |
|
<pre class="literal-block"> |
|
trial test_doughnut |
|
test_doughnut |
|
DoughnutTest |
|
test_default ... [OK] |
|
test_old_doughnuts ... [OK] |
|
test_old_maplebars ... [OK] |
|
test_tasty ... [OK] |
|
|
|
------------------------------------------------------------------------------- |
|
Ran 4 tests in 0.026s |
|
|
|
PASSED (successes=4) |
|
</pre> |
|
<p>Now let's clean up <tt class="docutils literal">tasty()</tt>:</p> |
|
<div class="highlight"><pre><span class="c"># flour.py snippet</span> |
|
|
|
<span class="k">def</span> <span class="nf">tasty</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> |
|
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">kind</span> <span class="o">==</span> <span class="s">'maple'</span><span class="p">:</span> |
|
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">age</span> <span class="o">></span> <span class="mi">5</span><span class="p">:</span> |
|
<span class="k">return</span> <span class="bp">False</span> |
|
<span class="k">elif</span> <span class="bp">self</span><span class="o">.</span><span class="n">age</span> <span class="o">></span> <span class="mi">2</span><span class="p">:</span> |
|
<span class="k">return</span> <span class="bp">False</span> |
|
<span class="k">return</span> <span class="bp">True</span> |
|
</pre></div> |
|
<p>The tests still pass:</p> |
|
<pre class="literal-block"> |
|
test_doughnut |
|
DoughnutTest |
|
test_default ... [OK] |
|
test_old_doughnuts ... [OK] |
|
test_old_maplebars ... [OK] |
|
test_tasty ... [OK] |
|
|
|
------------------------------------------------------------------------------- |
|
Ran 4 tests in 0.026s |
|
|
|
PASSED (successes=4) |
|
</pre> |
|
<p>Requirements 1 and 2 are now implemented. After test-driving requirements 3, |
|
4 and 5, we end up with a fully armed and operational doughnut:</p> |
|
<div class="highlight"><pre><span class="c"># test_doughnut.py</span> |
|
<span class="kn">from</span> <span class="nn">unittest</span> <span class="kn">import</span> <span class="n">TestCase</span> |
|
|
|
<span class="kn">from</span> <span class="nn">flour</span> <span class="kn">import</span> <span class="n">Doughnut</span><span class="p">,</span> <span class="n">Questionable</span> |
|
|
|
<span class="k">class</span> <span class="nc">DoughnutTest</span><span class="p">(</span><span class="n">TestCase</span><span class="p">):</span> |
|
|
|
|
|
<span class="k">def</span> <span class="nf">test_default</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> |
|
<span class="n">d</span> <span class="o">=</span> <span class="n">Doughnut</span><span class="p">()</span> |
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">d</span><span class="o">.</span><span class="n">kind</span><span class="p">,</span> <span class="s">'glazed'</span><span class="p">)</span> |
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">d</span><span class="o">.</span><span class="n">age</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> |
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">d</span><span class="o">.</span><span class="n">free</span><span class="p">,</span> <span class="bp">False</span><span class="p">)</span> |
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">d</span><span class="o">.</span><span class="n">location</span><span class="p">,</span> <span class="s">'box'</span><span class="p">)</span> |
|
|
|
|
|
<span class="k">def</span> <span class="nf">test_tasty</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> |
|
<span class="n">d</span> <span class="o">=</span> <span class="n">Doughnut</span><span class="p">()</span> |
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">d</span><span class="o">.</span><span class="n">tasty</span><span class="p">(),</span> <span class="bp">True</span><span class="p">)</span> |
|
|
|
|
|
<span class="k">def</span> <span class="nf">getOld</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> |
|
<span class="n">d</span> <span class="o">=</span> <span class="n">Doughnut</span><span class="p">()</span> |
|
<span class="n">d</span><span class="o">.</span><span class="n">age</span> <span class="o">=</span> <span class="mi">3</span> |
|
<span class="k">return</span> <span class="n">d</span> |
|
|
|
|
|
<span class="k">def</span> <span class="nf">test_old_doughnuts</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> |
|
<span class="n">d</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">getOld</span><span class="p">()</span> |
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">d</span><span class="o">.</span><span class="n">tasty</span><span class="p">(),</span> <span class="bp">False</span><span class="p">)</span> |
|
|
|
|
|
<span class="k">def</span> <span class="nf">test_old_maplebars</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> |
|
<span class="n">d</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">getOld</span><span class="p">()</span> |
|
<span class="n">d</span><span class="o">.</span><span class="n">kind</span> <span class="o">=</span> <span class="s">'maple'</span> |
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">d</span><span class="o">.</span><span class="n">tasty</span><span class="p">(),</span> <span class="bp">True</span><span class="p">)</span> |
|
|
|
<span class="n">d</span><span class="o">.</span><span class="n">age</span> <span class="o">=</span> <span class="mi">6</span> |
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">d</span><span class="o">.</span><span class="n">tasty</span><span class="p">(),</span> <span class="bp">False</span><span class="p">)</span> |
|
|
|
|
|
<span class="k">def</span> <span class="nf">getDumpster</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> |
|
<span class="n">d</span> <span class="o">=</span> <span class="n">Doughnut</span><span class="p">()</span> |
|
<span class="n">d</span><span class="o">.</span><span class="n">location</span> <span class="o">=</span> <span class="s">'dumpster'</span> |
|
<span class="k">return</span> <span class="n">d</span> |
|
|
|
|
|
<span class="k">def</span> <span class="nf">test_dumpster</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> |
|
<span class="n">d</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">getDumpster</span><span class="p">()</span> |
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">d</span><span class="o">.</span><span class="n">tasty</span><span class="p">(),</span> <span class="bp">False</span><span class="p">)</span> |
|
|
|
|
|
<span class="k">def</span> <span class="nf">test_free_dumpster</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> |
|
<span class="n">d</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">getDumpster</span><span class="p">()</span> |
|
<span class="n">d</span><span class="o">.</span><span class="n">free</span> <span class="o">=</span> <span class="bp">True</span> |
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">d</span><span class="o">.</span><span class="n">tasty</span><span class="p">(),</span> <span class="bp">True</span><span class="p">)</span> |
|
|
|
|
|
<span class="k">def</span> <span class="nf">test_apple_fritters</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> |
|
<span class="n">d</span> <span class="o">=</span> <span class="n">Doughnut</span><span class="p">()</span> |
|
<span class="n">d</span><span class="o">.</span><span class="n">kind</span> <span class="o">=</span> <span class="s">'apple fritter'</span> |
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertRaises</span><span class="p">(</span><span class="n">Questionable</span><span class="p">,</span> <span class="n">d</span><span class="o">.</span><span class="n">tasty</span><span class="p">)</span> |
|
</pre></div> |
|
<div class="highlight"><pre><span class="c"># flour.py</span> |
|
|
|
<span class="k">class</span> <span class="nc">Questionable</span><span class="p">(</span><span class="ne">Exception</span><span class="p">):</span> <span class="k">pass</span> |
|
|
|
<span class="k">class</span> <span class="nc">Doughnut</span><span class="p">:</span> |
|
|
|
<span class="n">kind</span> <span class="o">=</span> <span class="s">'glazed'</span> |
|
<span class="n">age</span> <span class="o">=</span> <span class="mi">0</span> |
|
<span class="n">free</span> <span class="o">=</span> <span class="bp">False</span> |
|
<span class="n">location</span> <span class="o">=</span> <span class="s">'box'</span> |
|
|
|
|
|
<span class="k">def</span> <span class="nf">tasty</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> |
|
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">kind</span> <span class="o">==</span> <span class="s">'apple fritter'</span><span class="p">:</span> |
|
<span class="k">raise</span> <span class="n">Questionable</span><span class="p">(</span><span class="s">'Apple fritters are of questionable tastiness'</span><span class="p">)</span> |
|
<span class="k">elif</span> <span class="bp">self</span><span class="o">.</span><span class="n">kind</span> <span class="o">==</span> <span class="s">'maple'</span><span class="p">:</span> |
|
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">age</span> <span class="o">></span> <span class="mi">5</span><span class="p">:</span> |
|
<span class="k">return</span> <span class="bp">False</span> |
|
<span class="k">elif</span> <span class="bp">self</span><span class="o">.</span><span class="n">age</span> <span class="o">></span> <span class="mi">2</span><span class="p">:</span> |
|
<span class="k">return</span> <span class="bp">False</span> |
|
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">location</span> <span class="o">==</span> <span class="s">'dumpster'</span><span class="p">:</span> |
|
<span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">free</span><span class="p">:</span> |
|
<span class="k">return</span> <span class="bp">False</span> |
|
<span class="k">return</span> <span class="bp">True</span> |
|
</pre></div> |
|
</div> |
|
</div> |
|
<div class="section" id="make-the-least-change-possible"> |
|
<h1>Make the least change possible</h1> |
|
<p>When trying to first make <tt class="docutils literal">test_old_doughnuts</tt> pass, we could have explicitly |
|
limited oldness to 'glazed' doughnuts:</p> |
|
<div class="highlight"><pre><span class="c"># flour.py snippet</span> |
|
|
|
<span class="k">def</span> <span class="nf">tasty</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> |
|
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">kind</span> <span class="o">==</span> <span class="s">'glazed'</span><span class="p">:</span> |
|
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">age</span> <span class="o">></span> <span class="mi">2</span><span class="p">:</span> |
|
<span class="k">return</span> <span class="bp">False</span> |
|
<span class="k">return</span> <span class="bp">True</span> |
|
</pre></div> |
|
<p>When passing <tt class="docutils literal">test_dumpster</tt> we could likewise have assumed that it should |
|
be for 'glazed' doughnuts. Doing so, we could end up with this version:</p> |
|
<div class="highlight"><pre><span class="c"># flour.py snippet</span> |
|
|
|
<span class="k">def</span> <span class="nf">tasty</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> |
|
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">kind</span> <span class="o">==</span> <span class="s">'glazed'</span><span class="p">:</span> |
|
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">age</span> <span class="o">></span> <span class="mi">2</span><span class="p">:</span> |
|
<span class="k">return</span> <span class="bp">False</span> |
|
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">location</span> <span class="o">==</span> <span class="s">'dumpster'</span><span class="p">:</span> |
|
<span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">free</span><span class="p">:</span> |
|
<span class="k">return</span> <span class="bp">False</span> |
|
<span class="k">elif</span> <span class="bp">self</span><span class="o">.</span><span class="n">kind</span> <span class="o">==</span> <span class="s">'apple fritter'</span><span class="p">:</span> |
|
<span class="k">raise</span> <span class="n">Questionable</span><span class="p">()</span> |
|
<span class="k">elif</span> <span class="bp">self</span><span class="o">.</span><span class="n">age</span> <span class="o">></span> <span class="mi">5</span><span class="p">:</span> |
|
<span class="k">return</span> <span class="bp">False</span> |
|
<span class="k">return</span> <span class="bp">True</span> |
|
</pre></div> |
|
<p>The tests pass, but the code is wrong according to requirements 1, 3 and 4. |
|
Make the smallest change possible when writing code to pass the tests.</p> |
|
</div> |
|
<div class="section" id="test-writing-test-driving"> |
|
<h1>Test writing != test driving</h1> |
|
<p>Tests are like a car. You can either use the car to drive you where you want |
|
to go, or you can tie a rope to the car and pull it with you.</p> |
|
<img alt="http://www.acraftphoto.com/blogger/2009/2009.09.30.truckpull.049.jpg" src="http://www.acraftphoto.com/blogger/2009/2009.09.30.truckpull.049.jpg" /> |
|
<p>If you pull the car around long enough, you'll eventually abandon it because |
|
it's too burdensome.</p> |
|
<p>"Having tests" is not the same as test-driven development. Don't say, "I have |
|
tests." Instead say, "I test-drove."</p> |
|
</div> |
|
<div class="section" id="coverage"> |
|
<h1>Coverage</h1> |
|
<p>In general, TDD leads to significantly better code coverage, and code coverage |
|
provides an indication as to whether something was test driven. For |
|
simple code, coverage can usually be 100%.</p> |
|
<p>See <a class="reference external" href="managing.html">Managing</a> for how to run coverage.</p> |
|
</div> |
|
<div class="section" id="what-other-people-say"> |
|
<h1>What other people say</h1> |
|
<ul class="simple"> |
|
<li><a class="reference external" href="http://www.agiledata.org/essays/tdd.html">http://www.agiledata.org/essays/tdd.html</a></li> |
|
<li><a class="reference external" href="http://en.wikipedia.org/wiki/Test-driven_development">http://en.wikipedia.org/wiki/Test-driven_development</a></li> |
|
<li><a class="reference external" href="http://bazaar.launchpad.net/~exarkun/+junk/training/view/head:/testing/tdd_outline.txt">http://bazaar.launchpad.net/~exarkun/+junk/training/view/head:/testing/tdd_outline.txt</a></li> |
|
</ul> |
|
</div> |
|
</div> |
|
</body> |
|
</html> |