forked from rachelslaybaugh/python-testing
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path09-tdd.html
More file actions
159 lines (151 loc) · 9.7 KB
/
09-tdd.html
File metadata and controls
159 lines (151 loc) · 9.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="generator" content="pandoc">
<title>Software Carpentry: Testing</title>
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" type="text/css" href="css/bootstrap/bootstrap.css" />
<link rel="stylesheet" type="text/css" href="css/bootstrap/bootstrap-theme.css" />
<link rel="stylesheet" type="text/css" href="css/swc.css" />
<link rel="alternate" type="application/rss+xml" title="Software Carpentry Blog" href="http://software-carpentry.org/feed.xml"/>
<meta charset="UTF-8" />
<!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
</head>
<body class="lesson">
<div class="container card">
<div class="banner">
<a href="http://software-carpentry.org" title="Software Carpentry">
<img alt="Software Carpentry banner" src="img/software-carpentry-banner.png" />
</a>
</div>
<article>
<div class="row">
<div class="col-md-10 col-md-offset-1">
<a href="index.html"><h1 class="title">Testing</h1></a>
<h2 class="subtitle">Test Driven Development</h2>
<section class="objectives panel panel-warning">
<div class="panel-heading">
<h2><span class="glyphicon glyphicon-certificate"></span>Learning Objectives</h2>
</div>
<div class="panel-body">
<ul>
<li>Learn about the benefits and drawbacks of Test Driven Development</li>
<li>Write a test before writing the code</li>
</ul>
</div>
</section>
<p>Test-driven Development (TDD) takes the workflow of writing code and writing tests and turns it on its head. TDD is a software development process where you write the tests first. Before you write a single line of a function, you first write the test for that function.</p>
<p>After you write a test, you are then allowed to proceed to write the function that you are testing. However, you are only supposed to implement enough of the function so that the test passes. If the function does not do what is needed, you write another test and then go back and modify the function. You repeat this process of test-then-implement until the function is completely implemented for your current needs.</p>
<aside class="callout panel panel-info">
<div class="panel-heading">
<h2><span class="glyphicon glyphicon-pushpin"></span>The Big Idea</h2>
</div>
<div class="panel-body">
<p>This design philosophy was most strongly put forth by Kent Beck in his book <em>Test-Driven Development: By Example</em>.</p>
<p>The central claim to TDD is that at the end of the process you have an implementation that is well tested for your use case, and the process itself is more efficient. You stop when your tests pass and you do not need any more features. You do not spend any time implementing options and features on the off chance that they will prove helpful later. You get what you need when you need it, and no more. TDD is a very powerful idea, though it can be hard to follow religiously.</p>
</div>
</aside>
<p>The most important takeaway from test-driven development is that the moment you start writing code, you should be considering how to test that code. The tests should be written and presented in tandem with the implementation. <strong>Testing is too important to be an afterthought.</strong></p>
<aside class="callout panel panel-info">
<div class="panel-heading">
<h2><span class="glyphicon glyphicon-pushpin"></span>You Do You</h2>
</div>
<div class="panel-body">
<p>Developers who practice strict TDD will tell you that it is the best thing since sliced arrays. However, do what works for you. The choice whether to pursue classic TDD is a personal decision.</p>
</div>
</aside>
<p>The following example illustrates classic TDD for a standard deviation function, <code>std()</code>.</p>
<p>To start, we write a test for computing the standard deviation from a list of numbers as follows:</p>
<pre class="sourceCode python"><code class="sourceCode python"><span class="ch">from</span> nose.tools <span class="ch">import</span> assert_equal
<span class="ch">from</span> mod <span class="ch">import</span> std
<span class="kw">def</span> test_std1():
obs = std([<span class="fl">0.0</span>, <span class="fl">2.0</span>])
exp = <span class="fl">1.0</span>
assert_equal(obs, exp)</code></pre>
<p>Next, we write the <em>minimal</em> version of <code>std()</code> that will cause <code>test_std1()</code> to pass:</p>
<pre class="sourceCode python"><code class="sourceCode python"><span class="kw">def</span> std(vals):
<span class="co"># surely this is cheating...</span>
<span class="kw">return</span> <span class="fl">1.0</span></code></pre>
<p>As you can see, the minimal version simply returns the expected result for the sole case that we are testing. If we only ever want to take the standard deviation of the numbers 0.0 and 2.0, or 1.0 and 3.0, and so on, then this implementation will work perfectly. If we want to branch out, then we probably need to write more robust code. However, before we can write more code, we first need to add another test or two:</p>
<pre class="sourceCode python"><code class="sourceCode python"><span class="kw">def</span> test_std1():
obs = std([<span class="fl">0.0</span>, <span class="fl">2.0</span>])
exp = <span class="fl">1.0</span>
assert_equal(obs, exp)
<span class="kw">def</span> test_std2():
<span class="co"># Test the fiducial case when we pass in an empty list.</span>
obs = std([])
exp = <span class="fl">0.0</span>
assert_equal(obs, exp)
<span class="kw">def</span> test_std3():
<span class="co"># Test a real case where the answer is not one.</span>
obs = std([<span class="fl">0.0</span>, <span class="fl">4.0</span>])
exp = <span class="fl">2.0</span>
assert_equal(obs, exp)</code></pre>
<p>A simple function implementation that would make these tests pass could be as follows:</p>
<pre class="sourceCode python"><code class="sourceCode python"><span class="kw">def</span> std(vals):
<span class="co"># a little better</span>
<span class="kw">if</span> <span class="dt">len</span>(vals) == <span class="dv">0</span>: <span class="co"># Special case the empty list.</span>
<span class="kw">return</span> <span class="fl">0.0</span>
<span class="kw">return</span> vals[-<span class="dv">1</span>] / <span class="fl">2.0</span> <span class="co"># By being clever, we can get away without doing real work.</span></code></pre>
<p>Are we done? No. Of course not. Even though the tests all pass, this is clearly still not a generic standard deviation function. To create a better implementation, TDD states that we again need to expand the test suite:</p>
<pre class="sourceCode python"><code class="sourceCode python"><span class="kw">def</span> test_std1():
obs = std([<span class="fl">0.0</span>, <span class="fl">2.0</span>])
exp = <span class="fl">1.0</span>
assert_equal(obs, exp)
<span class="kw">def</span> test_std2():
obs = std([])
exp = <span class="fl">0.0</span>
assert_equal(obs, exp)
<span class="kw">def</span> test_std3():
obs = std([<span class="fl">0.0</span>, <span class="fl">4.0</span>])
exp = <span class="fl">2.0</span>
assert_equal(obs, exp)
<span class="kw">def</span> test_std4():
<span class="co"># The first value is not zero.</span>
obs = std([<span class="fl">1.0</span>, <span class="fl">3.0</span>])
exp = <span class="fl">1.0</span>
assert_equal(obs, exp)
<span class="kw">def</span> test_std5():
<span class="co"># Here, we have more than two values, but all of the values are the same.</span>
obs = std([<span class="fl">1.0</span>, <span class="fl">1.0</span>, <span class="fl">1.0</span>])
exp = <span class="fl">0.0</span>
assert_equal(obs, exp)</code></pre>
<p>At this point, we may as well try to implement a generic standard deviation function. Recall:</p>
<div class="figure">
<img src="img/std.png" alt="Standard Deviation" /><p class="caption">Standard Deviation</p>
</div>
<p>We would spend more time trying to come up with clever approximations to the standard deviation than we would spend actually coding it.</p>
<section class="challenge panel panel-success">
<div class="panel-heading">
<h2><span class="glyphicon glyphicon-pencil"></span>Just Bite the Bullet</h2>
</div>
<div class="panel-body">
<ol style="list-style-type: decimal">
<li>Copy the five tests above into a file called test_std.py</li>
<li>Open mod.py</li>
<li>Add an implementation that actually calculates a standard deviation.</li>
<li>Run the tests above. Did they pass?</li>
</ol>
</div>
</section>
<p>It is important to note that we could improve this function by writing further tests. For example, this <code>std()</code> ignores the situation where infinity is an element of the values list. There is always more that can be tested. TDD prevents you from going overboard by telling you to stop testing when you have achieved all of your use cases.</p>
</div>
</div>
</article>
<div class="footer">
<a class="label swc-blue-bg" href="http://software-carpentry.org">Software Carpentry</a>
<a class="label swc-blue-bg" href="https://github.com/swcarpentry/lesson-template">Source</a>
<a class="label swc-blue-bg" href="LICENSE.html">License</a>
</div>
</div>
<!-- Javascript placed at the end of the document so the pages load faster -->
<script src="http://software-carpentry.org/v5/js/jquery-1.9.1.min.js"></script>
<script src="css/bootstrap/bootstrap-js/bootstrap.js"></script>
</body>
</html>