Converted exercises and tutorial for thw-testing to ipython notebook.
authorAnthony Scopatz <scopatz@gmail.com>
Mon, 9 Sep 2013 23:00:18 +0000 (01:00 +0200)
committerW. Trevor King <wking@tremily.us>
Sat, 9 Nov 2013 15:53:26 +0000 (07:53 -0800)
lessons/thw-testing/exercises.ipynb [new file with mode: 0644]
lessons/thw-testing/tutorial.ipynb [new file with mode: 0644]

diff --git a/lessons/thw-testing/exercises.ipynb b/lessons/thw-testing/exercises.ipynb
new file mode 100644 (file)
index 0000000..9d6d854
--- /dev/null
@@ -0,0 +1,195 @@
+{
+ "metadata": {
+  "name": ""
+ },
+ "nbformat": 3,
+ "nbformat_minor": 0,
+ "worksheets": [
+  {
+   "cells": [
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "# Testing Exercises\n",
+      "\n",
+      "The following exercises do not contain solutions. Yet. Instead, we will be\n",
+      "asking you to submit your solutions to these exercises and then we will post up\n",
+      "solutions at the start of next week. We encourage you to discuss your approaches\n",
+      "or solutions on the course forum!"
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "## Exercise 1: Mileage\n",
+      "\n",
+      "The function 'convert_mileage' converts miles per gallon (US style) to\u00c2 liters\n",
+      "per 100 km (metric style):"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "gal_to_litre = 3.78541178\n",
+      "mile_to_km = 1.609344\n",
+      "    \n",
+      "def convert_mileage(mpg):\n",
+      "    '''Converts miles per gallon to liters per 100 km'''\n",
+      "    litres_per_100_km = 100 / mpg / mile_to_km * gal_to_litre\n",
+      "    return litres_per_100_km"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "Create a subdirectory in your version control directory called `testing-exercises`, then\n",
+      "copy this function into a file in that directory called `mileage.py`. Add more\n",
+      "code to that file to repeatedly ask the user for a mileage in miles per gallon,\n",
+      "and output the mileage in liters per 100 km, until the user enters the string\n",
+      "`q`. You will need to use the `float()` function to convert from string to a\n",
+      "floating point number. Use the '`if __name__ == \"__main__\":`' trick to ensure\n",
+      "that the module can be imported without executing your testing code.\n",
+      "\n",
+      "1. Copy `mileage.py` to create `tryexcept.py` Add a try/except block to the new\n",
+      "program to display a helpful message instead of crashing when users enter\n",
+      "invalid input (such as the number \"0\" or the name of their favorite hockey\n",
+      "team). \n",
+      "\n",
+      "2. Reading the function again, you realize that accepting 0 or negative values\n",
+      "make no sense and should be reported as an error. Look at the exceptions defined\n",
+      "in the `exceptions` module (use the built-in `help(...)` or `dir(...)`\n",
+      "functions) and decide which of Python's built-in exceptions is most appropriate\n",
+      "to use for invalid input. Create a copy of 'tryexcept.py' called 'raiser.py'\n",
+      "that raises this exception; modify the main body of your program to catch it;\n",
+      "and add a comment inside the file explaining why you chose the exception you\n",
+      "did. (Note: you have to call this file `raiser.py`, not `raise.py` because\n",
+      "'import raise' is an error. \u00c2 Can you see why?)\n",
+      "\n",
+      "3. [According to Google](http://www.google.ca/search?q=20+miles+per+gallon+in+litres+per+100+km&gbv=1),\n",
+      "20 miles per gallon are equivalent to 11.7607292 liters per 100 km. Use these\n",
+      "values to write a unit test. Keep in mind that these floating values are subject\n",
+      "to truncation and rounding errors. Save the test case in a file called\n",
+      "`test_mileage.py` and run it using the `nosetests` command. \u00c2 Note:\n",
+      "`test_mileage.py` should use '`from raiser import convert_mileage`' to get the\n",
+      "final version of your mileage conversion function.\n",
+      "\n",
+      "4. Now add a second test case, for 40 miles per gallon equivalent to 5.88036458\n",
+      "liters per 100 km and run the tests again. \u00c2 Unless you have already fixed the\n",
+      "error that was present in the initial function, your test should fail. \u00c2 Find\n",
+      "and fix the error; submit your new function in a file called 'final_mileage.py'. "
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "## Exercise 2: Testing Averages\n",
+      "\n",
+      "The results of a set of experiments are stored in a file, where the _i-th_ line\n",
+      "stores the results of the _i-th_ experiment as a comma-separated list of\n",
+      "integers. A student is assigned the task of finding the experiment with the\n",
+      "smallest average value. She writes the following code: "
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "def avg_line(line):\n",
+      "    values = line.split(',')\n",
+      "    count = 0\n",
+      "    total = 0\n",
+      "    for value in values:\n",
+      "        total += int(value)\n",
+      "        count += 1\n",
+      "    return total / count\n",
+      "    \n",
+      "def min_avg(file_name):\n",
+      "    contents = open(file_name)\n",
+      "    averages = []\n",
+      "    for (i, line) in enumerate(contents):\n",
+      "        averages.append((avg_line(line), i))\n",
+      "    contents.close()\n",
+      "    averages.sort()\n",
+      "    min_avg, experiment_number = averages[0]\n",
+      "    return experiment_number"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "1. Refactor `min_avg` so that it can be tested without depending on external\n",
+      "files. Submit your code in a file called `first_averages.py`.\n",
+      "\n",
+      "2. Write Nose test cases for both functions. Consider what should happen if the\n",
+      "file is empty. Submit your tests in a file called `test_first_averages.py`.\n",
+      "Note: you may assume for now that all input is well formatted, i.e., you do\n",
+      "_not_ have to worry about empty lines, lines containing the names of hockey\n",
+      "teams, etc.\n",
+      "\n",
+      "3. The given specification is ambiguous: what should the result be if two or\n",
+      "more experiments are tied for the minimum average?  Copy 'first_averages.py' to\n",
+      "create a new file 'second_averages.py'; modify it to handle this case; add a\n",
+      "comment to the top explaining what rule you decided to use; and create a file\n",
+      "'test_second_averages.py' that tests your changes.\n",
+      "\n",
+      "4. Another student proposed an alternative implementation of the min_avg\n",
+      "function:"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "def min_avg(file_name):\n",
+      "    contents = open(file_name).readlines()\n",
+      "    min_avg = avg_line(contents[0])\n",
+      "    min_index = 0\n",
+      "    for (i,line) in enumerate(contents):\n",
+      "        current_avg = avg_line(line)\n",
+      "        if current_avg <= min_avg:\n",
+      "            min_avg = current_avg\n",
+      "            min_index = i\n",
+      "    return min_index"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "This implementation also finds an experiment with the smallest average, but\n",
+      "possibly a different one than the your function.  Modify your test cases so that\n",
+      "both your implementation and this one will pass.  (Hint: use the 'in' operator.)\n",
+      "\n",
+      "5. One way to avoid the ambiguity of this specification is to define a\n",
+      "'min_avg_all' function instead, which returns a list with all the experiments\n",
+      "with the smallest average, and let the caller select one. Write tests for the\n",
+      "'min_avg_all' function, considering the following situations: an empty file,\n",
+      "exactly one experiment with minimum average, and more than one experiment with\n",
+      "minimum average. Keep in mind that in the last case, implementations could\n",
+      "return the list in different order. Write the tests the file \"test_averages.py\".\n",
+      "Use the same data as for the previous tests, if possible. You should use\n",
+      "variables to avoid code duplication. You don't need to implement the\n",
+      "'min_avg_all' function, but your test cases should be comprehensive enough to\n",
+      "serve as a specification for it."
+     ]
+    }
+   ],
+   "metadata": {}
+  }
+ ]
+}
\ No newline at end of file
diff --git a/lessons/thw-testing/tutorial.ipynb b/lessons/thw-testing/tutorial.ipynb
new file mode 100644 (file)
index 0000000..f33eb67
--- /dev/null
@@ -0,0 +1,813 @@
+{
+ "metadata": {
+  "name": ""
+ },
+ "nbformat": 3,
+ "nbformat_minor": 0,
+ "worksheets": [
+  {
+   "cells": [
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "# Testing\n",
+      "\n",
+      "**Based on materials by Katy Huff, Rachel Slaybaugh, and Anthony Scopatz**"
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "## What is testing?\n",
+      "\n",
+      "Software testing is a process by which one or more expected behaviors\n",
+      "and results from a piece of software are exercised and confirmed. Well\n",
+      "chosen tests will confirm expected code behavior for the extreme\n",
+      "boundaries of the input domains, output ranges, parametric combinations,\n",
+      "and other behavioral **edge cases**."
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "## Why test software?\n",
+      "\n",
+      "Unless you write flawless, bug-free, perfectly accurate, fully precise,\n",
+      "and predictable code *every time*, you must test your code in order to\n",
+      "trust it enough to answer in the affirmative to at least a few of the\n",
+      "following questions:\n",
+      "\n",
+      "-  Does your code work?\n",
+      "-  **Always?**\n",
+      "-  Does it do what you think it does? ([Patriot Missile Failure](http://www.ima.umn.edu/~arnold/disasters/patriot.html))\n",
+      "-  Does it continue to work after changes are made?\n",
+      "-  Does it continue to work after system configurations or libraries\n",
+      "   are upgraded?\n",
+      "-  Does it respond properly for a full range of input parameters?\n",
+      "-  What about **edge and corner cases**?\n",
+      "-  What's the limit on that input parameter?\n",
+      "-  How will it affect your\n",
+      "   [publications](http://www.nature.com/news/2010/101013/full/467775a.html)?"
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "### Verification\n",
+      "\n",
+      "*Verification* is the process of asking, \"Have we built the software\n",
+      "correctly?\" That is, is the code bug free, precise, accurate, and\n",
+      "repeatable?"
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "### Validation\n",
+      "\n",
+      "*Validation* is the process of asking, \"Have we built the right\n",
+      "software?\" That is, is the code designed in such a way as to produce the\n",
+      "answers we are interested in, data we want, etc."
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "### Uncertainty Quantification\n",
+      "\n",
+      "*Uncertainty Quantification* is the process of asking, \"Given that our\n",
+      "algorithm may not be deterministic, was our execution within acceptable\n",
+      "error bounds?\" This is particularly important for anything which uses\n",
+      "random numbers, eg Monte Carlo methods."
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "## Where are tests?\n",
+      "\n",
+      "Say we have an averaging function:"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "def mean(numlist):\n",
+      "    total = sum(numlist)\n",
+      "    length = len(numlist)\n",
+      "    return total/length"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "Tests could be implemented as runtime *exceptions in the function*:"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "def mean(numlist):\n",
+      "    try:\n",
+      "        total = sum(numlist)\n",
+      "        length = len(numlist)\n",
+      "    except TypeError:\n",
+      "        raise TypeError(\"The number list was not a list of numbers.\")\n",
+      "    except:\n",
+      "        print \"There was a problem evaluating the number list.\"\n",
+      "    return total/length"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "Sometimes tests are functions alongside the function definitions\n",
+      "they are testing."
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "def mean(numlist):\n",
+      "    try:\n",
+      "        total = sum(numlist)\n",
+      "        length = len(numlist)\n",
+      "    except TypeError:\n",
+      "        raise TypeError(\"The number list was not a list of numbers.\")\n",
+      "    except:\n",
+      "        print \"There was a problem evaluating the number list.\"\n",
+      "    return total/length\n",
+      "\n",
+      "\n",
+      "def test_mean():\n",
+      "    assert mean([0, 0, 0, 0]) == 0\n",
+      "    assert mean([0, 200]) == 100\n",
+      "    assert mean([0, -200]) == -100\n",
+      "    assert mean([0]) == 0\n",
+      "\n",
+      "\n",
+      "def test_floating_mean():\n",
+      "    assert mean([1, 2]) == 1.5"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "Sometimes tests live in an executable independent of the main executable.\n",
+      "\n",
+      "**Implementation File:** `mean.py`"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "def mean(numlist):\n",
+      "    try:\n",
+      "        total = sum(numlist)\n",
+      "        length = len(numlist)\n",
+      "    except TypeError:\n",
+      "        raise TypeError(\"The number list was not a list of numbers.\")\n",
+      "    except:\n",
+      "        print \"There was a problem evaluating the number list.\"\n",
+      "    return total/length"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "**Test File:** `test_mean.py`"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "from stats import mean\n",
+      "\n",
+      "def test_mean():\n",
+      "    assert mean([0, 0, 0, 0]) == 0\n",
+      "    assert mean([0, 200]) == 100\n",
+      "    assert mean([0, -200]) == -100\n",
+      "    assert mean([0]) == 0\n",
+      "\n",
+      "\n",
+      "def test_floating_mean():\n",
+      "    assert mean([1, 2]) == 1.5"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "## When should we test?\n",
+      "\n",
+      "The three right answers are:\n",
+      "\n",
+      "- **ALWAYS!**\n",
+      "- **EARLY!**\n",
+      "- **OFTEN!**\n",
+      "\n",
+      "The longer answer is that testing either before or after your software\n",
+      "is written will improve your code, but testing after your program is\n",
+      "used for something important is too late.\n",
+      "\n",
+      "If we have a robust set of tests, we can run them before adding\n",
+      "something new and after adding something new. If the tests give the same\n",
+      "results (as appropriate), we can have some assurance that we didn't\n",
+      "wreak anything. The same idea applies to making changes in your system\n",
+      "configuration, updating support codes, etc.\n",
+      "\n",
+      "Another important feature of testing is that it helps you remember what\n",
+      "all the parts of your code do. If you are working on a large project\n",
+      "over three years and you end up with 200 classes, it may be hard to\n",
+      "remember what the widget class does in detail. If you have a test that\n",
+      "checks all of the widget's functionality, you can look at the test to\n",
+      "remember what it's supposed to do."
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "## Who should test?\n",
+      "\n",
+      "In a collaborative coding environment, where many developers contribute\n",
+      "to the same code base, developers should be responsible individually for\n",
+      "testing the functions they create and collectively for testing the code\n",
+      "as a whole.\n",
+      "\n",
+      "Professionals often test their code, and take pride in test coverage,\n",
+      "the percent of their functions that they feel confident are\n",
+      "comprehensively tested."
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "## How are tests written?\n",
+      "\n",
+      "The type of tests that are written is determined by the testing\n",
+      "framework you adopt. Don't worry, there are a lot of choices."
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "### Types of Tests\n",
+      "\n",
+      "**Exceptions:** Exceptions can be thought of as type of runtime test.\n",
+      "They alert the user to exceptional behavior in the code. Often,\n",
+      "exceptions are related to functions that depend on input that is unknown\n",
+      "at compile time. Checks that occur within the code to handle exceptional\n",
+      "behavior that results from this type of input are called Exceptions.\n",
+      "\n",
+      "**Unit Tests:** Unit tests are a type of test which test the fundamental\n",
+      "units of a program's functionality. Often, this is on the class or\n",
+      "function level of detail. However what defines a *code unit* is not\n",
+      "formally defined.\n",
+      "\n",
+      "To test functions and classes, the interfaces (API) - rather than the\n",
+      "implementation - should be tested. Treating the implementation as a\n",
+      "black box, we can probe the expected behavior with boundary cases for\n",
+      "the inputs.\n",
+      "\n",
+      "**System Tests:** System level tests are intended to test the code as a\n",
+      "whole. As opposed to unit tests, system tests ask for the behavior as a\n",
+      "whole. This sort of testing involves comparison with other validated\n",
+      "codes, analytical solutions, etc.\n",
+      "\n",
+      "**Regression Tests:** A regression test ensures that new code does\n",
+      "change anything. If you change the default answer, for example, or add a\n",
+      "new question, you'll need to make sure that missing entries are still\n",
+      "found and fixed.\n",
+      "\n",
+      "**Integration Tests:** Integration tests query the ability of the code\n",
+      "to integrate well with the system configuration and third party\n",
+      "libraries and modules. This type of test is essential for codes that\n",
+      "depend on libraries which might be updated independently of your code or\n",
+      "when your code might be used by a number of users who may have various\n",
+      "versions of libraries.\n",
+      "\n",
+      "**Test Suites:** Putting a series of unit tests into a collection of\n",
+      "modules creates, a test suite. Typically the suite as a whole is\n",
+      "executed (rather than each test individually) when verifying that the\n",
+      "code base still functions after changes have been made."
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "# Elements of a Test\n",
+      "\n",
+      "**Behavior:** The behavior you want to test. For example, you might want\n",
+      "to test the fun() function.\n",
+      "\n",
+      "**Expected Result:** This might be a single number, a range of numbers,\n",
+      "a new fully defined object, a system state, an exception, etc. When we\n",
+      "run the fun() function, we expect to generate some fun. If we don't\n",
+      "generate any fun, the fun() function should fail its test.\n",
+      "Alternatively, if it does create some fun, the fun() function should\n",
+      "pass this test. The the expected result should known *a priori*. For\n",
+      "numerical functions, this is result is ideally analytically determined\n",
+      "even if the function being tested isn't.\n",
+      "\n",
+      "**Assertions:** Require that some conditional be true. If the\n",
+      "conditional is false, the test fails.\n",
+      "\n",
+      "**Fixtures:** Sometimes you have to do some legwork to create the\n",
+      "objects that are necessary to run one or many tests. These objects are\n",
+      "called fixtures as they are not really part of the test themselves but\n",
+      "rather involve getting the computer into the appropriate state.\n",
+      "\n",
+      "For example, since fun varies a lot between people, the fun() function\n",
+      "is a method of the Person class. In order to check the fun function,\n",
+      "then, we need to create an appropriate Person object on which to run\n",
+      "fun().\n",
+      "\n",
+      "**Setup and teardown:** Creating fixtures is often done in a call to a\n",
+      "setup function. Deleting them and other cleanup is done in a teardown\n",
+      "function.\n",
+      "\n",
+      "**The Big Picture:** Putting all this together, the testing algorithm is\n",
+      "often:\n",
+      "\n",
+      "    setup()\n",
+      "    test()\n",
+      "    teardown()\n",
+      "\n",
+      "But, sometimes it's the case that your tests change the fixtures. If so,\n",
+      "it's better for the setup() and teardown() functions to occur on either\n",
+      "side of each test. In that case, the testing algorithm should be:\n",
+      "\n",
+      "    setup()\n",
+      "    test1()\n",
+      "    teardown()\n",
+      "\n",
+      "    setup()\n",
+      "    test2()\n",
+      "    teardown()\n",
+      "\n",
+      "    setup()\n",
+      "    test3()\n",
+      "    teardown()"
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "## Nose: A Python Testing Framework\n",
+      "\n",
+      "The testing framework we'll discuss today is called nose. However, there are\n",
+      "several other testing frameworks available in most language. Most notably there\n",
+      "is [JUnit](http://www.junit.org/) in Java which can arguably attributed to\n",
+      "inventing the testing framework. Google also provides a [test\n",
+      "framework](code.google.com/p/googletest/) for C++ applications (note, there's\n",
+      "also [CTest](http://cmake.org/Wiki/CMake/Testing_With_CTest)).  There\n",
+      "is at least one testing framework for R:\n",
+      "[testthat](http://cran.r-project.org/web/packages/testthat/index.html)."
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "### Where do nose tests live?\n",
+      "\n",
+      "Nose tests are files that begin with `Test-`, `Test_`, `test-`, or\n",
+      "`test_`. Specifically, these satisfy the testMatch regular expression\n",
+      "`[Tt]est[-_]`. (You can also teach nose to find tests by declaring them\n",
+      "in the unittest.TestCase subclasses chat you create in your code. You\n",
+      "can also create test functions which are not unittest.TestCase\n",
+      "subclasses if they are named with the configured testMatch regular\n",
+      "expression.)"
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "### Nose Test Syntax\n",
+      "\n",
+      "To write a nose test, we make assertions.\n",
+      "\n",
+      "    assert should_be_true()\n",
+      "    assert not should_not_be_true()\n",
+      "\n",
+      "Additionally, nose itself defines number of assert functions which can\n",
+      "be used to test more specific aspects of the code base.\n",
+      "\n",
+      "    from nose.tools import *\n",
+      "\n",
+      "    assert_equal(a, b)\n",
+      "    assert_almost_equal(a, b)\n",
+      "    assert_true(a)\n",
+      "    assert_false(a)\n",
+      "    assert_raises(exception, func, *args, **kwargs)\n",
+      "    assert_is_instance(a, b)\n",
+      "    # and many more!\n",
+      "\n",
+      "Moreover, numpy offers similar testing functions for arrays:\n",
+      "\n",
+      "    from numpy.testing import *\n",
+      "\n",
+      "    assert_array_equal(a, b)\n",
+      "    assert_array_almost_equal(a, b)\n",
+      "    # etc."
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "## Exercise: Writing tests for mean()\n",
+      "\n",
+      "There are a few tests for the mean() function that we listed in this\n",
+      "lesson. What are some tests that should fail? Add at least three test\n",
+      "cases to this set. Edit the `test_mean.py` file which tests the mean()\n",
+      "function in `mean.py`.\n",
+      "\n",
+      "*Hint:* Think about what form your input could take and what you should\n",
+      "do to handle it. Also, think about the type of the elements in the list.\n",
+      "What should be done if you pass a list of integers? What if you pass a\n",
+      "list of strings?\n",
+      "\n",
+      "**Example**:\n",
+      "\n",
+      "    $ nosetests test_mean.py"
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "## Test Driven Development\n",
+      "\n",
+      "Test driven development (TDD) is a philosophy whereby the developer\n",
+      "creates code by **writing the tests first**. That is to say you write the\n",
+      "tests *before* writing the associated code!\n",
+      "\n",
+      "This is an iterative process whereby you write a test then write the\n",
+      "minimum amount code to make the test pass. If a new feature is needed,\n",
+      "another test is written and the code is expanded to meet this new use\n",
+      "case. This continues until the code does what is needed.\n",
+      "\n",
+      "TDD operates on the YAGNI principle (You Ain't Gonna Need It). People\n",
+      "who diligently follow TDD swear by its effectiveness. This development\n",
+      "style was put forth most strongly by [Kent Beck in\n",
+      "2002](http://www.amazon.com/Test-Driven-Development-By-Example/dp/0321146530)."
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "### A TDD Example\n",
+      "\n",
+      "Say you want to write a std() function which computes the [Standard \n",
+      "Deviation](http://en.wikipedia.org/wiki/Standard_deviation). You\n",
+      "would - of course - start by writing the test, possibly testing a single set of \n",
+      "numbers:"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "from nose.tools import assert_equal, assert_almost_equal\n",
+      "\n",
+      "def test_std1():\n",
+      "    obs = std([0.0, 2.0])\n",
+      "    exp = 1.0\n",
+      "    assert_equal(obs, exp)"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "You would *then* go ahead and write the actual function:"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "def std(vals):\n",
+      "    # you snarky so-and-so\n",
+      "    return 1.0"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "And that is it, right?! Well, not quite. This implementation fails for\n",
+      "most other values. Adding tests we see that:"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "def test_std1():\n",
+      "    obs = std([0.0, 2.0])\n",
+      "    exp = 1.0\n",
+      "    assert_equal(obs, exp)\n",
+      "\n",
+      "def test_std2():\n",
+      "    obs = std([])\n",
+      "    exp = 0.0\n",
+      "    assert_equal(obs, exp)\n",
+      "\n",
+      "def test_std3():\n",
+      "    obs = std([0.0, 4.0])\n",
+      "    exp = 2.0\n",
+      "    assert_equal(obs, exp)"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "These extra tests now require that we bother to implement at least a slightly \n",
+      "more reasonable function:"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "def std(vals):\n",
+      "    # a little better\n",
+      "    if len(vals) == 0:\n",
+      "        return 0.0\n",
+      "    return vals[-1] / 2.0"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "However, this function still fails whenever vals has more than two elements or\n",
+      "the first element is not zero. Time for more tests!"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "def test_std1():\n",
+      "    obs = std([0.0, 2.0])\n",
+      "    exp = 1.0\n",
+      "    assert_equal(obs, exp)\n",
+      "\n",
+      "def test_std2():\n",
+      "    obs = std([])\n",
+      "    exp = 0.0\n",
+      "    assert_equal(obs, exp)\n",
+      "\n",
+      "def test_std3():\n",
+      "    obs = std([0.0, 4.0])\n",
+      "    exp = 2.0\n",
+      "    assert_equal(obs, exp)\n",
+      "\n",
+      "def test_std4():\n",
+      "    obs = std([1.0, 3.0])\n",
+      "    exp = 1.0\n",
+      "    assert_equal(obs, exp)\n",
+      "\n",
+      "def test_std5():\n",
+      "    obs = std([1.0, 1.0, 1.0])\n",
+      "    exp = 0.0\n",
+      "    assert_equal(obs, exp)"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "At this point, we had better go ahead and try do the right thing..."
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "def std(vals):\n",
+      "    # finally, some math\n",
+      "    n = len(vals)\n",
+      "    if n == 0:\n",
+      "        return 0.0\n",
+      "    mu = sum(vals) / n\n",
+      "    var = 0.0\n",
+      "    for val in vals:\n",
+      "        var = var + (val - mu)**2\n",
+      "    return (var / n)**0.5"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "Here it becomes very tempting to take an extended coffee break or\n",
+      "possibly a power lunch. But then you remember those pesky infinite values!\n",
+      "Perhaps the right thing to do here is to just be undefined.  Infinity in \n",
+      "Python may be represented by any literal float greater than or equal to 1e309."
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "def test_std1():\n",
+      "    obs = std([0.0, 2.0])\n",
+      "    exp = 1.0\n",
+      "    assert_equal(obs, exp)\n",
+      "\n",
+      "def test_std2():\n",
+      "    obs = std([])\n",
+      "    exp = 0.0\n",
+      "    assert_equal(obs, exp)\n",
+      "\n",
+      "def test_std3():\n",
+      "    obs = std([0.0, 4.0])\n",
+      "    exp = 2.0\n",
+      "    assert_equal(obs, exp)\n",
+      "\n",
+      "def test_std4():\n",
+      "    obs = std([1.0, 3.0])\n",
+      "    exp = 1.0\n",
+      "    assert_equal(obs, exp)\n",
+      "\n",
+      "def test_std5():\n",
+      "    obs = std([1.0, 1.0, 1.0])\n",
+      "    exp = 0.0\n",
+      "    assert_equal(obs, exp)\n",
+      "\n",
+      "def test_std6():\n",
+      "    obs = std([1e500])\n",
+      "    exp = NotImplemented\n",
+      "    assert_equal(obs, exp)\n",
+      "\n",
+      "def test_std7():\n",
+      "    obs = std([0.0, 1e4242])\n",
+      "    exp = NotImplemented\n",
+      "    assert_equal(obs, exp)"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "This means that it is time to add the appropriate case to the function\n",
+      "itself:"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "def std(vals):\n",
+      "    # sequence and you shall find\n",
+      "    n = len(vals)\n",
+      "    if n == 0:\n",
+      "        return 0.0\n",
+      "    mu = sum(vals) / n\n",
+      "    if mu == 1e500:\n",
+      "        return NotImplemented\n",
+      "    var = 0.0\n",
+      "    for val in vals:\n",
+      "        var = var + (val - mu)**2\n",
+      "    return (var / n)**0.5"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "## Quality Assurance Exercise\n",
+      "\n",
+      "Can you think of other tests to make for the std() function? I promise there\n",
+      "are at least two.\n",
+      "\n",
+      "1. How about std(string) or std(array)?\n",
+      "2. How about std(None)?\n",
+      "\n",
+      "Implement one new test in test_stats.py, run nosetests, and if it fails, implement\n",
+      "a more robust function for that case.\n",
+      "\n",
+      "And thus - finally - we have a robust function together with working\n",
+      "tests!"
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "## Further Statistics Tests\n",
+      "\n",
+      "The `stats.py` and `test_stats.py` files contain stubs for other simple statistics \n",
+      "functions: variance, median, and mode.  Try your new test-driven development chops\n",
+      "by implementing one or more of these functions along with their corresponding tests."
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "## Advanced Exercise\n",
+      "\n",
+      "**The Problem:** In 2D or 3D, we have two points (p1 and p2) which\n",
+      "define a line segment. Additionally there exists experimental data which\n",
+      "can be anywhere in the domain. Find the data point which is closest to\n",
+      "the line segment.\n",
+      "\n",
+      "In the `close_line.py` file there are four different implementations\n",
+      "which all solve this problem. [You can read more about them\n",
+      "here.](http://inscight.org/2012/03/31/evolution_of_a_solution/) However,\n",
+      "there are no tests! Please write from scratch a `test_close_line.py`\n",
+      "file which tests the closest\\_data\\_to\\_line() functions.\n",
+      "\n",
+      "*Hint:* you can use one implementation function to test another. Below\n",
+      "is some sample data to help you get started.\n",
+      "\n",
+      "![image](https://github.com/UW-Madison-ACI/boot-camps/raw/2013-04-uwmadison/python/testing/evo_sol1.png)"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "import numpy as np\n",
+      "\n",
+      "p1 = np.array([0.0, 0.0])\n",
+      "p2 = np.array([1.0, 1.0])\n",
+      "data = np.array([[0.3, 0.6], [0.25, 0.5], [1.0, 0.75]])"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    }
+   ],
+   "metadata": {}
+  }
+ ]
+}
\ No newline at end of file