"language": "python",
"metadata": {},
"outputs": [],
- "prompt_number": 11
+ "prompt_number": 12
},
{
"cell_type": "markdown",
"output_type": "pyerr",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[0;31mAssertionError\u001b[0m Traceback (most recent call last)",
- "\u001b[0;32m<ipython-input-12-2d797333ff4c>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;32massert\u001b[0m \u001b[0;36m4\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0madd_two_plus_two\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"2 + 2 didn't equal 4\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 7\u001b[0;31m \u001b[0mtest_add_two_plus_two_equals_four\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
- "\u001b[0;32m<ipython-input-12-2d797333ff4c>\u001b[0m in \u001b[0;36mtest_add_two_plus_two_equals_four\u001b[0;34m()\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mtest_add_two_plus_two_equals_four\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 5\u001b[0;31m \u001b[0;32massert\u001b[0m \u001b[0;36m4\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0madd_two_plus_two\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"2 + 2 didn't equal 4\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 6\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 7\u001b[0m \u001b[0mtest_add_two_plus_two_equals_four\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;32m<ipython-input-13-2d797333ff4c>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;32massert\u001b[0m \u001b[0;36m4\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0madd_two_plus_two\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"2 + 2 didn't equal 4\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 7\u001b[0;31m \u001b[0mtest_add_two_plus_two_equals_four\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
+ "\u001b[0;32m<ipython-input-13-2d797333ff4c>\u001b[0m in \u001b[0;36mtest_add_two_plus_two_equals_four\u001b[0;34m()\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mtest_add_two_plus_two_equals_four\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 5\u001b[0;31m \u001b[0;32massert\u001b[0m \u001b[0;36m4\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0madd_two_plus_two\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"2 + 2 didn't equal 4\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 6\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 7\u001b[0m \u001b[0mtest_add_two_plus_two_equals_four\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;31mAssertionError\u001b[0m: 2 + 2 didn't equal 4"
]
}
],
- "prompt_number": 12
+ "prompt_number": 13
},
{
"cell_type": "markdown",
"language": "python",
"metadata": {},
"outputs": [],
- "prompt_number": 13
+ "prompt_number": 14
},
{
"cell_type": "code",
"outputs": [
{
"html": [
- "<div id=\"ipython_nose_bfec549d740f4b038703369ae3f06e87\"></div>"
+ "<div id=\"ipython_nose_d38e10d9d55741bd9baa798e352d80ea\"></div>"
],
"output_type": "display_data"
},
{
"javascript": [
- "document.ipython_nose_bfec549d740f4b038703369ae3f06e87 = $(\"#ipython_nose_bfec549d740f4b038703369ae3f06e87\");"
+ "document.ipython_nose_d38e10d9d55741bd9baa798e352d80ea = $(\"#ipython_nose_d38e10d9d55741bd9baa798e352d80ea\");"
],
"output_type": "display_data"
},
{
"javascript": [
- "document.ipython_nose_bfec549d740f4b038703369ae3f06e87.append($(\"<span>.</span>\"));"
+ "document.ipython_nose_d38e10d9d55741bd9baa798e352d80ea.append($(\"<span>.</span>\"));"
],
"output_type": "display_data"
},
{
"javascript": [
- "document.ipython_nose_bfec549d740f4b038703369ae3f06e87.append($(\"<span>F</span>\"));"
+ "document.ipython_nose_d38e10d9d55741bd9baa798e352d80ea.append($(\"<span>F</span>\"));"
],
"output_type": "display_data"
},
{
"javascript": [
- "delete document.ipython_nose_bfec549d740f4b038703369ae3f06e87;"
+ "delete document.ipython_nose_d38e10d9d55741bd9baa798e352d80ea;"
],
"output_type": "display_data"
},
" testMethod()\n",
" File \"/Users/Jon/Applications/anaconda/lib/python2.7/site-packages/nose/case.py\", line 197, in runTest\n",
" self.test(*self.arg)\n",
- " File \"<<a href=\"#ipython-input-12-2d797333ff4c\">ipython-input-12-2d797333ff4c</a><script>\n",
- " $(\"div.prompt.input_prompt:contains([12])\")\n",
- " .attr(\"id\", \"ipython-input-12-2d797333ff4c\");\n",
+ " File \"<<a href=\"#ipython-input-13-2d797333ff4c\">ipython-input-13-2d797333ff4c</a><script>\n",
+ " $(\"div.prompt.input_prompt:contains([13])\")\n",
+ " .attr(\"id\", \"ipython-input-13-2d797333ff4c\");\n",
" </script>>\", line 5, in test_add_two_plus_two_equals_four\n",
" assert 4 == add_two_plus_two(), \"2 + 2 didn't equal 4\"\n",
"AssertionError: 2 + 2 didn't equal 4\n",
" "
],
"output_type": "pyout",
- "prompt_number": 14,
+ "prompt_number": 15,
"text": [
"1/2 tests passed; 1 failed\n",
"========\n",
" testMethod()\n",
" File \"/Users/Jon/Applications/anaconda/lib/python2.7/site-packages/nose/case.py\", line 197, in runTest\n",
" self.test(*self.arg)\n",
- " File \"<ipython-input-12-2d797333ff4c>\", line 5, in test_add_two_plus_two_equals_four\n",
+ " File \"<ipython-input-13-2d797333ff4c>\", line 5, in test_add_two_plus_two_equals_four\n",
" assert 4 == add_two_plus_two(), \"2 + 2 didn't equal 4\"\n",
"AssertionError: 2 + 2 didn't equal 4\n",
"\n"
]
}
],
- "prompt_number": 14
+ "prompt_number": 15
},
{
"cell_type": "markdown",
"text": [
"........\r\n",
"----------------------------------------------------------------------\r\n",
- "Ran 8 tests in 0.003s\r\n",
+ "Ran 8 tests in 0.004s\r\n",
"\r\n",
"OK\r\n"
]
}
],
- "prompt_number": 15
+ "prompt_number": 16
},
{
"cell_type": "markdown",
"outputs": [
{
"html": [
- "<div id=\"ipython_nose_7affb7b5cb134255ac605e091f8bd433\"></div>"
+ "<div id=\"ipython_nose_b902caf7914c4fb3b2efaf1c540ce8e0\"></div>"
],
"output_type": "display_data"
},
{
"javascript": [
- "document.ipython_nose_7affb7b5cb134255ac605e091f8bd433 = $(\"#ipython_nose_7affb7b5cb134255ac605e091f8bd433\");"
+ "document.ipython_nose_b902caf7914c4fb3b2efaf1c540ce8e0 = $(\"#ipython_nose_b902caf7914c4fb3b2efaf1c540ce8e0\");"
],
"output_type": "display_data"
},
{
"javascript": [
- "document.ipython_nose_7affb7b5cb134255ac605e091f8bd433.append($(\"<span>.</span>\"));"
+ "document.ipython_nose_b902caf7914c4fb3b2efaf1c540ce8e0.append($(\"<span>.</span>\"));"
],
"output_type": "display_data"
},
{
"javascript": [
- "document.ipython_nose_7affb7b5cb134255ac605e091f8bd433.append($(\"<span>.</span>\"));"
+ "document.ipython_nose_b902caf7914c4fb3b2efaf1c540ce8e0.append($(\"<span>.</span>\"));"
],
"output_type": "display_data"
},
{
"javascript": [
- "delete document.ipython_nose_7affb7b5cb134255ac605e091f8bd433;"
+ "delete document.ipython_nose_b902caf7914c4fb3b2efaf1c540ce8e0;"
],
"output_type": "display_data"
},
" "
],
"output_type": "pyout",
- "prompt_number": 17,
+ "prompt_number": 18,
"text": [
"2/2 tests passed\n"
]
}
],
- "prompt_number": 17
+ "prompt_number": 18
},
{
"cell_type": "markdown",
"***"
]
},
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "***\n",
+ "# Test-driven development\n",
+ "***\n",
+ "\n",
+ "We've seen how to refactor code, and we've seen how to write and run tests. We have code to read an animals file and to spit out the data. Next we need code to find the mean of a bunch of numbers, and we'll need code to filter the list of data to pull out only the animals that we want to count. We'll start by writing the `mean` function, and we'll start writing the `mean` function by writing a test.\n",
+ "\n",
+ "Why would we write the tests first? *Test-driven development (TDD)* is a fairly recent concept in software engineering. The idea is that you develop a particular piece of functionality in small, iterative chunks: write a test that fails, write some code to make the test pass, write another test that fails, write some more code to make the test pass, etcetera. This is believed to have the following benefits:\n",
+ "\n",
+ "* Test coverage is extremely high, because you won't write code unless prompted by a test.\n",
+ "* Code size is minimized, because you won't be tempted to \"throw in the kitchen sink\" when writing a function that strictly passes tests.\n",
+ "* Code complexity is reduced, because you can \"fearlessly refactor\" code if you have a battery of tests to prove that you haven't broken anything in the process.\n",
+ "* The tests serve as \"documentation\" for what a function does and does not do.\n",
+ "\n",
+ "The jury is still out on whether TDD is valuable enough to justify the extra ceremony, and some people consider it to be superfluous or trendy. The important thing is that your code is tested, whether the tests came first or not.\n",
+ "\n",
+ "Let's give it a try."
+ ]
+ },
{
"cell_type": "code",
"collapsed": false,
"input": [
- "def calc_mean(ilist):\n",
- " '''\n",
- " returns the mean of input list\n",
- " '''\n",
- " if len(ilist) == 0:\n",
- " raise ValueError('Input is empty.')\n",
- " \n",
- " total = 0.0\n",
- " for num in ilist:\n",
- " total = total + num\n",
- " return total/float(len(ilist))"
+ "def test_mean_of_zero():\n",
+ " assert 0 == mean([0])"
],
"language": "python",
"metadata": {},
"outputs": [],
- "prompt_number": 30
+ "prompt_number": 19
},
{
"cell_type": "code",
"collapsed": false,
"input": [
- "import nose.tools as nt"
+ "%nose"
+ ],
+ "language": "python",
+ "metadata": {},
+ "outputs": [
+ {
+ "html": [
+ "<div id=\"ipython_nose_a3c3d168eb2c4a988da0f4a492cbd197\"></div>"
+ ],
+ "output_type": "display_data"
+ },
+ {
+ "javascript": [
+ "document.ipython_nose_a3c3d168eb2c4a988da0f4a492cbd197 = $(\"#ipython_nose_a3c3d168eb2c4a988da0f4a492cbd197\");"
+ ],
+ "output_type": "display_data"
+ },
+ {
+ "javascript": [
+ "document.ipython_nose_a3c3d168eb2c4a988da0f4a492cbd197.append($(\"<span>E</span>\"));"
+ ],
+ "output_type": "display_data"
+ },
+ {
+ "javascript": [
+ "document.ipython_nose_a3c3d168eb2c4a988da0f4a492cbd197.append($(\"<span>.</span>\"));"
+ ],
+ "output_type": "display_data"
+ },
+ {
+ "javascript": [
+ "document.ipython_nose_a3c3d168eb2c4a988da0f4a492cbd197.append($(\"<span>.</span>\"));"
+ ],
+ "output_type": "display_data"
+ },
+ {
+ "javascript": [
+ "delete document.ipython_nose_a3c3d168eb2c4a988da0f4a492cbd197;"
+ ],
+ "output_type": "display_data"
+ },
+ {
+ "html": [
+ " <style type=\"text/css\">\n",
+ " span.nosefailedfunc {\n",
+ " font-family: monospace;\n",
+ " font-weight: bold;\n",
+ " }\n",
+ " div.noseresults {\n",
+ " width: 100%;\n",
+ " }\n",
+ " div.nosebar {\n",
+ " float: left;\n",
+ " padding: 1ex 0px 1ex 0px;\n",
+ " }\n",
+ " div.nosebar.fail {\n",
+ " background: #ff3019; /* Old browsers */\n",
+ " /* FF3.6+ */\n",
+ " background: -moz-linear-gradient(top, #ff3019 0%, #cf0404 100%);\n",
+ " /* Chrome,Safari4+ */\n",
+ " background: -webkit-gradient(linear, left top, left bottom,\n",
+ " color-stop(0%,#ff3019),\n",
+ " color-stop(100%,#cf0404));\n",
+ " /* Chrome10+,Safari5.1+ */\n",
+ " background: -webkit-linear-gradient(top, #ff3019 0%,#cf0404 100%);\n",
+ " /* Opera 11.10+ */\n",
+ " background: -o-linear-gradient(top, #ff3019 0%,#cf0404 100%);\n",
+ " /* IE10+ */\n",
+ " background: -ms-linear-gradient(top, #ff3019 0%,#cf0404 100%);\n",
+ " /* W3C */\n",
+ " background: linear-gradient(to bottom, #ff3019 0%,#cf0404 100%);\n",
+ " }\n",
+ " div.nosebar.pass {\n",
+ " background: #52b152;\n",
+ " background: -moz-linear-gradient(top, #52b152 1%, #008a00 100%);\n",
+ " background: -webkit-gradient(linear, left top, left bottom,\n",
+ " color-stop(1%,#52b152),\n",
+ " color-stop(100%,#008a00));\n",
+ " background: -webkit-linear-gradient(top, #52b152 1%,#008a00 100%);\n",
+ " background: -o-linear-gradient(top, #52b152 1%,#008a00 100%);\n",
+ " background: -ms-linear-gradient(top, #52b152 1%,#008a00 100%);\n",
+ " background: linear-gradient(to bottom, #52b152 1%,#008a00 100%);\n",
+ " }\n",
+ " div.nosebar.skip {\n",
+ " background: #f1e767;\n",
+ " background: -moz-linear-gradient(top, #f1e767 0%, #feb645 100%);\n",
+ " background: -webkit-gradient(linear, left top, left bottom,\n",
+ " color-stop(0%,#f1e767),\n",
+ " color-stop(100%,#feb645));\n",
+ " background: -webkit-linear-gradient(top, #f1e767 0%,#feb645 100%);\n",
+ " background: -o-linear-gradient(top, #f1e767 0%,#feb645 100%);\n",
+ " background: -ms-linear-gradient(top, #f1e767 0%,#feb645 100%);\n",
+ " background: linear-gradient(to bottom, #f1e767 0%,#feb645 100%);\n",
+ " }\n",
+ " div.nosebar.leftmost {\n",
+ " border-radius: 4px 0 0 4px;\n",
+ " }\n",
+ " div.nosebar.rightmost {\n",
+ " border-radius: 0 4px 4px 0;\n",
+ " }\n",
+ " div.nosefailbanner {\n",
+ " border-radius: 4px 0 0 4px;\n",
+ " border-left: 10px solid #cf0404;\n",
+ " padding: 0.5ex 0em 0.5ex 1em;\n",
+ " margin-top: 1ex;\n",
+ " margin-bottom: 0px;\n",
+ " }\n",
+ " div.nosefailbanner.expanded {\n",
+ " border-radius: 4px 4px 0 0;\n",
+ " border-top: 10px solid #cf0404;\n",
+ " }\n",
+ " pre.nosetraceback {\n",
+ " border-radius: 0 4px 4px 4px;\n",
+ " border-left: 10px solid #cf0404;\n",
+ " padding: 1em;\n",
+ " margin-left: 0px;\n",
+ " margin-top: 0px;\n",
+ " display: none;\n",
+ " }\n",
+ " </style>\n",
+ " \n",
+ " <script>\n",
+ " setTimeout(function () {\n",
+ " $('.nosefailtoggle').bind(\n",
+ " 'click',\n",
+ " function () {\n",
+ " $(\n",
+ " $(this)\n",
+ " .parent().toggleClass('expanded')\n",
+ " .parent()\n",
+ " .children()\n",
+ " .filter('.nosetraceback')\n",
+ " ).toggle();\n",
+ " }\n",
+ " );},\n",
+ " 0);\n",
+ " </script>\n",
+ " \n",
+ " <div class=\"noseresults\">\n",
+ " <div class=\"nosebar fail leftmost\" style=\"width: 33%\">\n",
+ " \n",
+ " </div>\n",
+ " <div class=\"nosebar skip\" style=\"width: 0%\">\n",
+ " \n",
+ " </div>\n",
+ " <div class=\"nosebar pass rightmost\" style=\"width: 67%\">\n",
+ " \n",
+ " </div>\n",
+ " 2/3 tests passed; 1 failed\n",
+ " </div>\n",
+ " \n",
+ " <div class=\"nosefailure\">\n",
+ " <div class=\"nosefailbanner\">\n",
+ " failed: <span class=\"nosefailedfunc\">__main__.test_mean_of_zero</span>\n",
+ " [<a class=\"nosefailtoggle\" href=\"#\">toggle traceback</a>]\n",
+ " </div>\n",
+ " <pre class=\"nosetraceback\">Traceback (most recent call last):\n",
+ " File \"/Users/Jon/Applications/anaconda/lib/python2.7/unittest/case.py\", line 331, in run\n",
+ " testMethod()\n",
+ " File \"/Users/Jon/Applications/anaconda/lib/python2.7/site-packages/nose/case.py\", line 197, in runTest\n",
+ " self.test(*self.arg)\n",
+ " File \"<<a href=\"#ipython-input-19-481e1c58db22\">ipython-input-19-481e1c58db22</a><script>\n",
+ " $(\"div.prompt.input_prompt:contains([19])\")\n",
+ " .attr(\"id\", \"ipython-input-19-481e1c58db22\");\n",
+ " </script>>\", line 2, in test_mean_of_zero\n",
+ " assert 0 == mean([0])\n",
+ "NameError: global name 'mean' is not defined\n",
+ "</pre>\n",
+ " </div>\n",
+ " "
+ ],
+ "output_type": "pyout",
+ "prompt_number": 20,
+ "text": [
+ "2/3 tests passed; 1 failed\n",
+ "========\n",
+ "__main__.test_mean_of_zero\n",
+ "========\n",
+ "Traceback (most recent call last):\n",
+ " File \"/Users/Jon/Applications/anaconda/lib/python2.7/unittest/case.py\", line 331, in run\n",
+ " testMethod()\n",
+ " File \"/Users/Jon/Applications/anaconda/lib/python2.7/site-packages/nose/case.py\", line 197, in runTest\n",
+ " self.test(*self.arg)\n",
+ " File \"<ipython-input-19-481e1c58db22>\", line 2, in test_mean_of_zero\n",
+ " assert 0 == mean([0])\n",
+ "NameError: global name 'mean' is not defined\n",
+ "\n"
+ ]
+ }
+ ],
+ "prompt_number": 20
+ },
+ {
+ "cell_type": "code",
+ "collapsed": false,
+ "input": [
+ "def mean():\n",
+ " pass"
],
"language": "python",
"metadata": {},
"outputs": [],
- "prompt_number": 27
+ "prompt_number": 21
+ },
+ {
+ "cell_type": "code",
+ "collapsed": false,
+ "input": [
+ "%nose"
+ ],
+ "language": "python",
+ "metadata": {},
+ "outputs": [
+ {
+ "html": [
+ "<div id=\"ipython_nose_bbb38260de4a452086abb22e7db22dfd\"></div>"
+ ],
+ "output_type": "display_data"
+ },
+ {
+ "javascript": [
+ "document.ipython_nose_bbb38260de4a452086abb22e7db22dfd = $(\"#ipython_nose_bbb38260de4a452086abb22e7db22dfd\");"
+ ],
+ "output_type": "display_data"
+ },
+ {
+ "javascript": [
+ "document.ipython_nose_bbb38260de4a452086abb22e7db22dfd.append($(\"<span>E</span>\"));"
+ ],
+ "output_type": "display_data"
+ },
+ {
+ "javascript": [
+ "document.ipython_nose_bbb38260de4a452086abb22e7db22dfd.append($(\"<span>.</span>\"));"
+ ],
+ "output_type": "display_data"
+ },
+ {
+ "javascript": [
+ "document.ipython_nose_bbb38260de4a452086abb22e7db22dfd.append($(\"<span>.</span>\"));"
+ ],
+ "output_type": "display_data"
+ },
+ {
+ "javascript": [
+ "delete document.ipython_nose_bbb38260de4a452086abb22e7db22dfd;"
+ ],
+ "output_type": "display_data"
+ },
+ {
+ "html": [
+ " <style type=\"text/css\">\n",
+ " span.nosefailedfunc {\n",
+ " font-family: monospace;\n",
+ " font-weight: bold;\n",
+ " }\n",
+ " div.noseresults {\n",
+ " width: 100%;\n",
+ " }\n",
+ " div.nosebar {\n",
+ " float: left;\n",
+ " padding: 1ex 0px 1ex 0px;\n",
+ " }\n",
+ " div.nosebar.fail {\n",
+ " background: #ff3019; /* Old browsers */\n",
+ " /* FF3.6+ */\n",
+ " background: -moz-linear-gradient(top, #ff3019 0%, #cf0404 100%);\n",
+ " /* Chrome,Safari4+ */\n",
+ " background: -webkit-gradient(linear, left top, left bottom,\n",
+ " color-stop(0%,#ff3019),\n",
+ " color-stop(100%,#cf0404));\n",
+ " /* Chrome10+,Safari5.1+ */\n",
+ " background: -webkit-linear-gradient(top, #ff3019 0%,#cf0404 100%);\n",
+ " /* Opera 11.10+ */\n",
+ " background: -o-linear-gradient(top, #ff3019 0%,#cf0404 100%);\n",
+ " /* IE10+ */\n",
+ " background: -ms-linear-gradient(top, #ff3019 0%,#cf0404 100%);\n",
+ " /* W3C */\n",
+ " background: linear-gradient(to bottom, #ff3019 0%,#cf0404 100%);\n",
+ " }\n",
+ " div.nosebar.pass {\n",
+ " background: #52b152;\n",
+ " background: -moz-linear-gradient(top, #52b152 1%, #008a00 100%);\n",
+ " background: -webkit-gradient(linear, left top, left bottom,\n",
+ " color-stop(1%,#52b152),\n",
+ " color-stop(100%,#008a00));\n",
+ " background: -webkit-linear-gradient(top, #52b152 1%,#008a00 100%);\n",
+ " background: -o-linear-gradient(top, #52b152 1%,#008a00 100%);\n",
+ " background: -ms-linear-gradient(top, #52b152 1%,#008a00 100%);\n",
+ " background: linear-gradient(to bottom, #52b152 1%,#008a00 100%);\n",
+ " }\n",
+ " div.nosebar.skip {\n",
+ " background: #f1e767;\n",
+ " background: -moz-linear-gradient(top, #f1e767 0%, #feb645 100%);\n",
+ " background: -webkit-gradient(linear, left top, left bottom,\n",
+ " color-stop(0%,#f1e767),\n",
+ " color-stop(100%,#feb645));\n",
+ " background: -webkit-linear-gradient(top, #f1e767 0%,#feb645 100%);\n",
+ " background: -o-linear-gradient(top, #f1e767 0%,#feb645 100%);\n",
+ " background: -ms-linear-gradient(top, #f1e767 0%,#feb645 100%);\n",
+ " background: linear-gradient(to bottom, #f1e767 0%,#feb645 100%);\n",
+ " }\n",
+ " div.nosebar.leftmost {\n",
+ " border-radius: 4px 0 0 4px;\n",
+ " }\n",
+ " div.nosebar.rightmost {\n",
+ " border-radius: 0 4px 4px 0;\n",
+ " }\n",
+ " div.nosefailbanner {\n",
+ " border-radius: 4px 0 0 4px;\n",
+ " border-left: 10px solid #cf0404;\n",
+ " padding: 0.5ex 0em 0.5ex 1em;\n",
+ " margin-top: 1ex;\n",
+ " margin-bottom: 0px;\n",
+ " }\n",
+ " div.nosefailbanner.expanded {\n",
+ " border-radius: 4px 4px 0 0;\n",
+ " border-top: 10px solid #cf0404;\n",
+ " }\n",
+ " pre.nosetraceback {\n",
+ " border-radius: 0 4px 4px 4px;\n",
+ " border-left: 10px solid #cf0404;\n",
+ " padding: 1em;\n",
+ " margin-left: 0px;\n",
+ " margin-top: 0px;\n",
+ " display: none;\n",
+ " }\n",
+ " </style>\n",
+ " \n",
+ " <script>\n",
+ " setTimeout(function () {\n",
+ " $('.nosefailtoggle').bind(\n",
+ " 'click',\n",
+ " function () {\n",
+ " $(\n",
+ " $(this)\n",
+ " .parent().toggleClass('expanded')\n",
+ " .parent()\n",
+ " .children()\n",
+ " .filter('.nosetraceback')\n",
+ " ).toggle();\n",
+ " }\n",
+ " );},\n",
+ " 0);\n",
+ " </script>\n",
+ " \n",
+ " <div class=\"noseresults\">\n",
+ " <div class=\"nosebar fail leftmost\" style=\"width: 33%\">\n",
+ " \n",
+ " </div>\n",
+ " <div class=\"nosebar skip\" style=\"width: 0%\">\n",
+ " \n",
+ " </div>\n",
+ " <div class=\"nosebar pass rightmost\" style=\"width: 67%\">\n",
+ " \n",
+ " </div>\n",
+ " 2/3 tests passed; 1 failed\n",
+ " </div>\n",
+ " \n",
+ " <div class=\"nosefailure\">\n",
+ " <div class=\"nosefailbanner\">\n",
+ " failed: <span class=\"nosefailedfunc\">__main__.test_mean_of_zero</span>\n",
+ " [<a class=\"nosefailtoggle\" href=\"#\">toggle traceback</a>]\n",
+ " </div>\n",
+ " <pre class=\"nosetraceback\">Traceback (most recent call last):\n",
+ " File \"/Users/Jon/Applications/anaconda/lib/python2.7/unittest/case.py\", line 331, in run\n",
+ " testMethod()\n",
+ " File \"/Users/Jon/Applications/anaconda/lib/python2.7/site-packages/nose/case.py\", line 197, in runTest\n",
+ " self.test(*self.arg)\n",
+ " File \"<<a href=\"#ipython-input-19-481e1c58db22\">ipython-input-19-481e1c58db22</a><script>\n",
+ " $(\"div.prompt.input_prompt:contains([19])\")\n",
+ " .attr(\"id\", \"ipython-input-19-481e1c58db22\");\n",
+ " </script>>\", line 2, in test_mean_of_zero\n",
+ " assert 0 == mean([0])\n",
+ "TypeError: mean() takes no arguments (1 given)\n",
+ "</pre>\n",
+ " </div>\n",
+ " "
+ ],
+ "output_type": "pyout",
+ "prompt_number": 22,
+ "text": [
+ "2/3 tests passed; 1 failed\n",
+ "========\n",
+ "__main__.test_mean_of_zero\n",
+ "========\n",
+ "Traceback (most recent call last):\n",
+ " File \"/Users/Jon/Applications/anaconda/lib/python2.7/unittest/case.py\", line 331, in run\n",
+ " testMethod()\n",
+ " File \"/Users/Jon/Applications/anaconda/lib/python2.7/site-packages/nose/case.py\", line 197, in runTest\n",
+ " self.test(*self.arg)\n",
+ " File \"<ipython-input-19-481e1c58db22>\", line 2, in test_mean_of_zero\n",
+ " assert 0 == mean([0])\n",
+ "TypeError: mean() takes no arguments (1 given)\n",
+ "\n"
+ ]
+ }
+ ],
+ "prompt_number": 22
+ },
+ {
+ "cell_type": "code",
+ "collapsed": false,
+ "input": [
+ "def mean(numbers):\n",
+ " pass"
+ ],
+ "language": "python",
+ "metadata": {},
+ "outputs": [],
+ "prompt_number": 23
+ },
+ {
+ "cell_type": "code",
+ "collapsed": false,
+ "input": [
+ "%nose"
+ ],
+ "language": "python",
+ "metadata": {},
+ "outputs": [
+ {
+ "html": [
+ "<div id=\"ipython_nose_183426a0661b43479529049b993ae958\"></div>"
+ ],
+ "output_type": "display_data"
+ },
+ {
+ "javascript": [
+ "document.ipython_nose_183426a0661b43479529049b993ae958 = $(\"#ipython_nose_183426a0661b43479529049b993ae958\");"
+ ],
+ "output_type": "display_data"
+ },
+ {
+ "javascript": [
+ "document.ipython_nose_183426a0661b43479529049b993ae958.append($(\"<span>F</span>\"));"
+ ],
+ "output_type": "display_data"
+ },
+ {
+ "javascript": [
+ "document.ipython_nose_183426a0661b43479529049b993ae958.append($(\"<span>.</span>\"));"
+ ],
+ "output_type": "display_data"
+ },
+ {
+ "javascript": [
+ "document.ipython_nose_183426a0661b43479529049b993ae958.append($(\"<span>.</span>\"));"
+ ],
+ "output_type": "display_data"
+ },
+ {
+ "javascript": [
+ "delete document.ipython_nose_183426a0661b43479529049b993ae958;"
+ ],
+ "output_type": "display_data"
+ },
+ {
+ "html": [
+ " <style type=\"text/css\">\n",
+ " span.nosefailedfunc {\n",
+ " font-family: monospace;\n",
+ " font-weight: bold;\n",
+ " }\n",
+ " div.noseresults {\n",
+ " width: 100%;\n",
+ " }\n",
+ " div.nosebar {\n",
+ " float: left;\n",
+ " padding: 1ex 0px 1ex 0px;\n",
+ " }\n",
+ " div.nosebar.fail {\n",
+ " background: #ff3019; /* Old browsers */\n",
+ " /* FF3.6+ */\n",
+ " background: -moz-linear-gradient(top, #ff3019 0%, #cf0404 100%);\n",
+ " /* Chrome,Safari4+ */\n",
+ " background: -webkit-gradient(linear, left top, left bottom,\n",
+ " color-stop(0%,#ff3019),\n",
+ " color-stop(100%,#cf0404));\n",
+ " /* Chrome10+,Safari5.1+ */\n",
+ " background: -webkit-linear-gradient(top, #ff3019 0%,#cf0404 100%);\n",
+ " /* Opera 11.10+ */\n",
+ " background: -o-linear-gradient(top, #ff3019 0%,#cf0404 100%);\n",
+ " /* IE10+ */\n",
+ " background: -ms-linear-gradient(top, #ff3019 0%,#cf0404 100%);\n",
+ " /* W3C */\n",
+ " background: linear-gradient(to bottom, #ff3019 0%,#cf0404 100%);\n",
+ " }\n",
+ " div.nosebar.pass {\n",
+ " background: #52b152;\n",
+ " background: -moz-linear-gradient(top, #52b152 1%, #008a00 100%);\n",
+ " background: -webkit-gradient(linear, left top, left bottom,\n",
+ " color-stop(1%,#52b152),\n",
+ " color-stop(100%,#008a00));\n",
+ " background: -webkit-linear-gradient(top, #52b152 1%,#008a00 100%);\n",
+ " background: -o-linear-gradient(top, #52b152 1%,#008a00 100%);\n",
+ " background: -ms-linear-gradient(top, #52b152 1%,#008a00 100%);\n",
+ " background: linear-gradient(to bottom, #52b152 1%,#008a00 100%);\n",
+ " }\n",
+ " div.nosebar.skip {\n",
+ " background: #f1e767;\n",
+ " background: -moz-linear-gradient(top, #f1e767 0%, #feb645 100%);\n",
+ " background: -webkit-gradient(linear, left top, left bottom,\n",
+ " color-stop(0%,#f1e767),\n",
+ " color-stop(100%,#feb645));\n",
+ " background: -webkit-linear-gradient(top, #f1e767 0%,#feb645 100%);\n",
+ " background: -o-linear-gradient(top, #f1e767 0%,#feb645 100%);\n",
+ " background: -ms-linear-gradient(top, #f1e767 0%,#feb645 100%);\n",
+ " background: linear-gradient(to bottom, #f1e767 0%,#feb645 100%);\n",
+ " }\n",
+ " div.nosebar.leftmost {\n",
+ " border-radius: 4px 0 0 4px;\n",
+ " }\n",
+ " div.nosebar.rightmost {\n",
+ " border-radius: 0 4px 4px 0;\n",
+ " }\n",
+ " div.nosefailbanner {\n",
+ " border-radius: 4px 0 0 4px;\n",
+ " border-left: 10px solid #cf0404;\n",
+ " padding: 0.5ex 0em 0.5ex 1em;\n",
+ " margin-top: 1ex;\n",
+ " margin-bottom: 0px;\n",
+ " }\n",
+ " div.nosefailbanner.expanded {\n",
+ " border-radius: 4px 4px 0 0;\n",
+ " border-top: 10px solid #cf0404;\n",
+ " }\n",
+ " pre.nosetraceback {\n",
+ " border-radius: 0 4px 4px 4px;\n",
+ " border-left: 10px solid #cf0404;\n",
+ " padding: 1em;\n",
+ " margin-left: 0px;\n",
+ " margin-top: 0px;\n",
+ " display: none;\n",
+ " }\n",
+ " </style>\n",
+ " \n",
+ " <script>\n",
+ " setTimeout(function () {\n",
+ " $('.nosefailtoggle').bind(\n",
+ " 'click',\n",
+ " function () {\n",
+ " $(\n",
+ " $(this)\n",
+ " .parent().toggleClass('expanded')\n",
+ " .parent()\n",
+ " .children()\n",
+ " .filter('.nosetraceback')\n",
+ " ).toggle();\n",
+ " }\n",
+ " );},\n",
+ " 0);\n",
+ " </script>\n",
+ " \n",
+ " <div class=\"noseresults\">\n",
+ " <div class=\"nosebar fail leftmost\" style=\"width: 33%\">\n",
+ " \n",
+ " </div>\n",
+ " <div class=\"nosebar skip\" style=\"width: 0%\">\n",
+ " \n",
+ " </div>\n",
+ " <div class=\"nosebar pass rightmost\" style=\"width: 67%\">\n",
+ " \n",
+ " </div>\n",
+ " 2/3 tests passed; 1 failed\n",
+ " </div>\n",
+ " \n",
+ " <div class=\"nosefailure\">\n",
+ " <div class=\"nosefailbanner\">\n",
+ " failed: <span class=\"nosefailedfunc\">__main__.test_mean_of_zero</span>\n",
+ " [<a class=\"nosefailtoggle\" href=\"#\">toggle traceback</a>]\n",
+ " </div>\n",
+ " <pre class=\"nosetraceback\">Traceback (most recent call last):\n",
+ " File \"/Users/Jon/Applications/anaconda/lib/python2.7/unittest/case.py\", line 331, in run\n",
+ " testMethod()\n",
+ " File \"/Users/Jon/Applications/anaconda/lib/python2.7/site-packages/nose/case.py\", line 197, in runTest\n",
+ " self.test(*self.arg)\n",
+ " File \"<<a href=\"#ipython-input-19-481e1c58db22\">ipython-input-19-481e1c58db22</a><script>\n",
+ " $(\"div.prompt.input_prompt:contains([19])\")\n",
+ " .attr(\"id\", \"ipython-input-19-481e1c58db22\");\n",
+ " </script>>\", line 2, in test_mean_of_zero\n",
+ " assert 0 == mean([0])\n",
+ "AssertionError\n",
+ "</pre>\n",
+ " </div>\n",
+ " "
+ ],
+ "output_type": "pyout",
+ "prompt_number": 24,
+ "text": [
+ "2/3 tests passed; 1 failed\n",
+ "========\n",
+ "__main__.test_mean_of_zero\n",
+ "========\n",
+ "Traceback (most recent call last):\n",
+ " File \"/Users/Jon/Applications/anaconda/lib/python2.7/unittest/case.py\", line 331, in run\n",
+ " testMethod()\n",
+ " File \"/Users/Jon/Applications/anaconda/lib/python2.7/site-packages/nose/case.py\", line 197, in runTest\n",
+ " self.test(*self.arg)\n",
+ " File \"<ipython-input-19-481e1c58db22>\", line 2, in test_mean_of_zero\n",
+ " assert 0 == mean([0])\n",
+ "AssertionError\n",
+ "\n"
+ ]
+ }
+ ],
+ "prompt_number": 24
+ },
+ {
+ "cell_type": "code",
+ "collapsed": false,
+ "input": [
+ "def mean(numbers):\n",
+ " return 0"
+ ],
+ "language": "python",
+ "metadata": {},
+ "outputs": [],
+ "prompt_number": 25
+ },
+ {
+ "cell_type": "code",
+ "collapsed": false,
+ "input": [
+ "%nose"
+ ],
+ "language": "python",
+ "metadata": {},
+ "outputs": [
+ {
+ "html": [
+ "<div id=\"ipython_nose_6748615aba7a4a3abecfdbad96ac3251\"></div>"
+ ],
+ "output_type": "display_data"
+ },
+ {
+ "javascript": [
+ "document.ipython_nose_6748615aba7a4a3abecfdbad96ac3251 = $(\"#ipython_nose_6748615aba7a4a3abecfdbad96ac3251\");"
+ ],
+ "output_type": "display_data"
+ },
+ {
+ "javascript": [
+ "document.ipython_nose_6748615aba7a4a3abecfdbad96ac3251.append($(\"<span>.</span>\"));"
+ ],
+ "output_type": "display_data"
+ },
+ {
+ "javascript": [
+ "document.ipython_nose_6748615aba7a4a3abecfdbad96ac3251.append($(\"<span>.</span>\"));"
+ ],
+ "output_type": "display_data"
+ },
+ {
+ "javascript": [
+ "document.ipython_nose_6748615aba7a4a3abecfdbad96ac3251.append($(\"<span>.</span>\"));"
+ ],
+ "output_type": "display_data"
+ },
+ {
+ "javascript": [
+ "delete document.ipython_nose_6748615aba7a4a3abecfdbad96ac3251;"
+ ],
+ "output_type": "display_data"
+ },
+ {
+ "html": [
+ " <style type=\"text/css\">\n",
+ " span.nosefailedfunc {\n",
+ " font-family: monospace;\n",
+ " font-weight: bold;\n",
+ " }\n",
+ " div.noseresults {\n",
+ " width: 100%;\n",
+ " }\n",
+ " div.nosebar {\n",
+ " float: left;\n",
+ " padding: 1ex 0px 1ex 0px;\n",
+ " }\n",
+ " div.nosebar.fail {\n",
+ " background: #ff3019; /* Old browsers */\n",
+ " /* FF3.6+ */\n",
+ " background: -moz-linear-gradient(top, #ff3019 0%, #cf0404 100%);\n",
+ " /* Chrome,Safari4+ */\n",
+ " background: -webkit-gradient(linear, left top, left bottom,\n",
+ " color-stop(0%,#ff3019),\n",
+ " color-stop(100%,#cf0404));\n",
+ " /* Chrome10+,Safari5.1+ */\n",
+ " background: -webkit-linear-gradient(top, #ff3019 0%,#cf0404 100%);\n",
+ " /* Opera 11.10+ */\n",
+ " background: -o-linear-gradient(top, #ff3019 0%,#cf0404 100%);\n",
+ " /* IE10+ */\n",
+ " background: -ms-linear-gradient(top, #ff3019 0%,#cf0404 100%);\n",
+ " /* W3C */\n",
+ " background: linear-gradient(to bottom, #ff3019 0%,#cf0404 100%);\n",
+ " }\n",
+ " div.nosebar.pass {\n",
+ " background: #52b152;\n",
+ " background: -moz-linear-gradient(top, #52b152 1%, #008a00 100%);\n",
+ " background: -webkit-gradient(linear, left top, left bottom,\n",
+ " color-stop(1%,#52b152),\n",
+ " color-stop(100%,#008a00));\n",
+ " background: -webkit-linear-gradient(top, #52b152 1%,#008a00 100%);\n",
+ " background: -o-linear-gradient(top, #52b152 1%,#008a00 100%);\n",
+ " background: -ms-linear-gradient(top, #52b152 1%,#008a00 100%);\n",
+ " background: linear-gradient(to bottom, #52b152 1%,#008a00 100%);\n",
+ " }\n",
+ " div.nosebar.skip {\n",
+ " background: #f1e767;\n",
+ " background: -moz-linear-gradient(top, #f1e767 0%, #feb645 100%);\n",
+ " background: -webkit-gradient(linear, left top, left bottom,\n",
+ " color-stop(0%,#f1e767),\n",
+ " color-stop(100%,#feb645));\n",
+ " background: -webkit-linear-gradient(top, #f1e767 0%,#feb645 100%);\n",
+ " background: -o-linear-gradient(top, #f1e767 0%,#feb645 100%);\n",
+ " background: -ms-linear-gradient(top, #f1e767 0%,#feb645 100%);\n",
+ " background: linear-gradient(to bottom, #f1e767 0%,#feb645 100%);\n",
+ " }\n",
+ " div.nosebar.leftmost {\n",
+ " border-radius: 4px 0 0 4px;\n",
+ " }\n",
+ " div.nosebar.rightmost {\n",
+ " border-radius: 0 4px 4px 0;\n",
+ " }\n",
+ " div.nosefailbanner {\n",
+ " border-radius: 4px 0 0 4px;\n",
+ " border-left: 10px solid #cf0404;\n",
+ " padding: 0.5ex 0em 0.5ex 1em;\n",
+ " margin-top: 1ex;\n",
+ " margin-bottom: 0px;\n",
+ " }\n",
+ " div.nosefailbanner.expanded {\n",
+ " border-radius: 4px 4px 0 0;\n",
+ " border-top: 10px solid #cf0404;\n",
+ " }\n",
+ " pre.nosetraceback {\n",
+ " border-radius: 0 4px 4px 4px;\n",
+ " border-left: 10px solid #cf0404;\n",
+ " padding: 1em;\n",
+ " margin-left: 0px;\n",
+ " margin-top: 0px;\n",
+ " display: none;\n",
+ " }\n",
+ " </style>\n",
+ " \n",
+ " <script>\n",
+ " setTimeout(function () {\n",
+ " $('.nosefailtoggle').bind(\n",
+ " 'click',\n",
+ " function () {\n",
+ " $(\n",
+ " $(this)\n",
+ " .parent().toggleClass('expanded')\n",
+ " .parent()\n",
+ " .children()\n",
+ " .filter('.nosetraceback')\n",
+ " ).toggle();\n",
+ " }\n",
+ " );},\n",
+ " 0);\n",
+ " </script>\n",
+ " \n",
+ " <div class=\"noseresults\">\n",
+ " <div class=\"nosebar fail leftmost\" style=\"width: 0%\">\n",
+ " \n",
+ " </div>\n",
+ " <div class=\"nosebar skip\" style=\"width: 0%\">\n",
+ " \n",
+ " </div>\n",
+ " <div class=\"nosebar pass rightmost\" style=\"width: 100%\">\n",
+ " \n",
+ " </div>\n",
+ " 3/3 tests passed\n",
+ " </div>\n",
+ " "
+ ],
+ "output_type": "pyout",
+ "prompt_number": 26,
+ "text": [
+ "3/3 tests passed\n"
+ ]
+ }
+ ],
+ "prompt_number": 26
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "When we follow this sequence, you can see how the tests *drive* the development of the `mean` function. You can also see that this can be tedious, especially for newbies."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Exercise: Write the `mean` function and its tests "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Write a function called `mean` that accepts a list of numbers and returns the arithmetic mean. It should work like this:\n",
+ "\n",
+ " In [1]: mean([5, 7, 15, 9, 0, 18])\n",
+ " Out [1]: 9.0\n",
+ "\n",
+ "Make sure to write a suite of tests for your implementation - either first or after the fact, whichever seems more logical to you. Some ideas about what to test:\n",
+ "\n",
+ "* A list with one number\n",
+ "* A list of all zeros\n",
+ "* Integers with an integral mean\n",
+ "* Integers with a non-integral mean\n",
+ "* Floating point numbers\n",
+ "\n",
+ "TBD: empty list? exception?"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "collapsed": false,
+ "input": [
+ "def calc_mean(ilist):\n",
+ " '''\n",
+ " returns the mean of input list\n",
+ " '''\n",
+ " if len(ilist) == 0:\n",
+ " raise ValueError('Input is empty.')\n",
+ " \n",
+ " total = 0.0\n",
+ " for num in ilist:\n",
+ " total = total + num\n",
+ " return total/float(len(ilist))"
+ ],
+ "language": "python",
+ "metadata": {},
+ "outputs": [],
+ "prompt_number": 27
+ },
+ {
+ "cell_type": "code",
+ "collapsed": false,
+ "input": [
+ "import nose.tools as nt"
+ ],
+ "language": "python",
+ "metadata": {},
+ "outputs": [
+ {
+ "output_type": "stream",
+ "stream": "stdout",
+ "text": [
+ "9.0\n",
+ "56\n"
+ ]
+ }
+ ],
+ "prompt_number": 31
},
{
"cell_type": "code",