Add practice section and examples
authorJon Speicher <jon.speicher@gmail.com>
Sun, 28 Jul 2013 00:25:59 +0000 (20:25 -0400)
committerW. Trevor King <wking@tremily.us>
Sat, 9 Nov 2013 18:27:52 +0000 (10:27 -0800)
python/sw_engineering/SoftwareEngineering.ipynb

index de261006d3785461845eb686f87b051cfa5d0402..b2ca6efe87d7592bf19fb84c8991e462e4974a06 100644 (file)
@@ -22,7 +22,8 @@
       "* Testing\n",
       "* Nose\n",
       "* Test-driven development\n",
-      "* Exceptions"
+      "* Exceptions\n",
+      "* Practice"
      ]
     },
     {
       "***"
      ]
     },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "***\n",
+      "# Practice\n",
+      "***\n",
+      "\n",
+      "## Filtering animals\n",
+      "\n",
+      "We have a function to read animals from a file and return lists containing the columns, and we have a function that calculates a mean for a list of numbers. What else do we need?"
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "## Exercise: Write the `filter_animals` function and its tests\n",
+      "\n",
+      "This function should:\n",
+      "\n",
+      "* Accept the name of the animal we are interested in catching in the filter\n",
+      "* Accept four lists containing unfiltered dates, times, animal names, and counts\n",
+      "* Return four lists containing dates, times, animal names, and counts, pertaining only to the animal that we wanted to catch in the filter\n",
+      "* Be tested!"
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "## Results\n",
+      "\n",
+      "When you are done, your code should look something like this."
+     ]
+    },
     {
      "cell_type": "code",
      "collapsed": false,
      "input": [
-      "def filter_animals(species, date, time, animal, count):\n",
-      "    \"\"\"\n",
-      "    Given a particular species, filter out the data for just that species.\n",
-      "\n",
-      "    Returns four lists: date, time, animal, count.\n",
-      "    \"\"\"\n",
-      "    fdate = []\n",
-      "    ftime = []\n",
-      "    fanimal = []\n",
-      "    fcount = []\n",
+      "def filter_animals(filtered_animal, dates, times, animals, counts):\n",
+      "    '''Given a particular species, filter out and return just the data for that species.'''\n",
+      "\n",
+      "    filtered_dates = []\n",
+      "    filtered_times = []\n",
+      "    filtered_animals = []\n",
+      "    filtered_counts = []\n",
       "    \n",
-      "    for d, t, a, c in zip(date, time, animal, count):\n",
-      "        if a == species:\n",
-      "            fdate.append(d)\n",
-      "            ftime.append(t)\n",
-      "            fanimal.append(a)\n",
-      "            fcount.append(c)\n",
+      "    for date, time, animal, count in zip(dates, times, animals, counts):\n",
+      "        if animal == filtered_animal:\n",
+      "            filtered_dates.append(date)\n",
+      "            filtered_times.append(time)\n",
+      "            filtered_animals.append(animal)\n",
+      "            filtered_counts.append(count)\n",
       "    \n",
-      "    return fdate, ftime, fanimal, fcount"
+      "    return filtered_dates, filtered_times, filtered_animals, filtered_counts"
      ],
      "language": "python",
      "metadata": {},
      "outputs": [],
-     "prompt_number": 36
+     "prompt_number": 48
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "Does it work?"
+     ]
     },
     {
      "cell_type": "code",
        ]
       }
      ],
-     "prompt_number": 33
+     "prompt_number": 49
     },
     {
      "cell_type": "code",
      "collapsed": false,
      "input": [
-      "def test_filter_animals1():\n",
-      "    date, time, animal, count = read_file('animals.txt')\n",
-      "    fdate, ftime, fanimal, fcount = filter_animals('Elk', date, time, animal, count)\n",
+      "dates, times, animals, counts = read_sightings_from_file('animals.txt')\n",
+      "print filter_animals('Elk', dates, times, animals, counts)"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        "(['2011-04-23', '2011-04-23'], ['14:12', '10:24'], ['Elk', 'Elk'], [25, 26])"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        "\n"
+       ]
+      }
+     ],
+     "prompt_number": 50
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "What do your tests look like?"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "def test_filter_finds_one_animal_in_list():\n",
+      "    sample_dates = ['2011-04-23']\n",
+      "    sample_times = ['14:12']\n",
+      "    sample_animals = ['Elk']\n",
+      "    sample_counts = [42]\n",
+      "    \n",
+      "    dates, times, animals, counts = filter_animals('Elk', sample_dates, sample_times, sample_animals, sample_counts)\n",
       "    \n",
-      "    assert fdate == ['2011-04-23', '2011-04-23']\n",
-      "    assert ftime == ['14:12', '10:24']\n",
-      "    assert fanimal == ['Elk', 'Elk']\n",
-      "    assert fcount == [25, 26]"
+      "    assert dates == sample_dates\n",
+      "    assert times == sample_times\n",
+      "    assert animals == sample_animals\n",
+      "    assert counts == sample_counts"
      ],
      "language": "python",
      "metadata": {},
      "outputs": [],
-     "prompt_number": 37
+     "prompt_number": 54
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "def test_filter_returns_empty_lists_if_animal_not_in_list():\n",
+      "    sample_dates = ['2011-04-03', '2011-04-04', '2011-04-04']\n",
+      "    sample_times = ['14:12', '18:32', '00:27']\n",
+      "    sample_animals = ['Moose', 'Wolverine']\n",
+      "    sample_counts = [18, 6]\n",
+      "    \n",
+      "    dates, times, animals, counts = filter_animals('Elk', sample_dates, sample_times, sample_animals, sample_counts)\n",
+      "    \n",
+      "    assert dates == []\n",
+      "    assert times == []\n",
+      "    assert animals == []\n",
+      "    assert counts == []"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [],
+     "prompt_number": 57
     },
     {
      "cell_type": "code",
      "outputs": [
       {
        "html": [
-        "<div id=\"ipython_nose_d965cb31197d482394af98b11319a703\"></div>"
+        "<div id=\"ipython_nose_421c36436bd1424eaeea6cc04f73853f\"></div>"
+       ],
+       "output_type": "display_data"
+      },
+      {
+       "javascript": [
+        "document.ipython_nose_421c36436bd1424eaeea6cc04f73853f = $(\"#ipython_nose_421c36436bd1424eaeea6cc04f73853f\");"
+       ],
+       "output_type": "display_data"
+      },
+      {
+       "javascript": [
+        "document.ipython_nose_421c36436bd1424eaeea6cc04f73853f.append($(\"<span>.</span>\"));"
+       ],
+       "output_type": "display_data"
+      },
+      {
+       "javascript": [
+        "document.ipython_nose_421c36436bd1424eaeea6cc04f73853f.append($(\"<span>.</span>\"));"
        ],
        "output_type": "display_data"
       },
       {
        "javascript": [
-        "document.ipython_nose_d965cb31197d482394af98b11319a703 = $(\"#ipython_nose_d965cb31197d482394af98b11319a703\");"
+        "document.ipython_nose_421c36436bd1424eaeea6cc04f73853f.append($(\"<span>.</span>\"));"
        ],
        "output_type": "display_data"
       },
       {
        "javascript": [
-        "document.ipython_nose_d965cb31197d482394af98b11319a703.append($(\"<span>.</span>\"));"
+        "document.ipython_nose_421c36436bd1424eaeea6cc04f73853f.append($(\"<span>.</span>\"));"
        ],
        "output_type": "display_data"
       },
       {
        "javascript": [
-        "document.ipython_nose_d965cb31197d482394af98b11319a703.append($(\"<span>.</span>\"));"
+        "document.ipython_nose_421c36436bd1424eaeea6cc04f73853f.append($(\"<span>.</span>\"));"
        ],
        "output_type": "display_data"
       },
       {
        "javascript": [
-        "document.ipython_nose_d965cb31197d482394af98b11319a703.append($(\"<span>.</span>\"));"
+        "document.ipython_nose_421c36436bd1424eaeea6cc04f73853f.append($(\"<span>.</span>\"));"
        ],
        "output_type": "display_data"
       },
       {
        "javascript": [
-        "document.ipython_nose_d965cb31197d482394af98b11319a703.append($(\"<span>.</span>\"));"
+        "document.ipython_nose_421c36436bd1424eaeea6cc04f73853f.append($(\"<span>.</span>\"));"
        ],
        "output_type": "display_data"
       },
       {
        "javascript": [
-        "document.ipython_nose_d965cb31197d482394af98b11319a703.append($(\"<span>.</span>\"));"
+        "document.ipython_nose_421c36436bd1424eaeea6cc04f73853f.append($(\"<span>.</span>\"));"
        ],
        "output_type": "display_data"
       },
       {
        "javascript": [
-        "document.ipython_nose_d965cb31197d482394af98b11319a703.append($(\"<span>.</span>\"));"
+        "document.ipython_nose_421c36436bd1424eaeea6cc04f73853f.append($(\"<span>.</span>\"));"
        ],
        "output_type": "display_data"
       },
       {
        "javascript": [
-        "delete document.ipython_nose_d965cb31197d482394af98b11319a703;"
+        "document.ipython_nose_421c36436bd1424eaeea6cc04f73853f.append($(\"<span>.</span>\"));"
+       ],
+       "output_type": "display_data"
+      },
+      {
+       "javascript": [
+        "delete document.ipython_nose_421c36436bd1424eaeea6cc04f73853f;"
        ],
        "output_type": "display_data"
       },
         "      <div class=\"nosebar pass rightmost\" style=\"width: 100%\">\n",
         "          &nbsp;\n",
         "      </div>\n",
-        "      6/6 tests passed\n",
+        "      10/10 tests passed\n",
         "    </div>\n",
         "    "
        ],
        "output_type": "pyout",
-       "prompt_number": 38,
+       "prompt_number": 58,
        "text": [
-        "6/6 tests passed\n"
+        "10/10 tests passed\n"
        ]
       }
      ],
-     "prompt_number": 38
+     "prompt_number": 58
     },
     {
-     "cell_type": "code",
-     "collapsed": false,
-     "input": [
-      "d, t, a, c = read_file('animals.txt')\n",
-      "filter_animals('Grizzly', d, t, a, c)"
-     ],
-     "language": "python",
+     "cell_type": "markdown",
      "metadata": {},
-     "outputs": [
-      {
-       "output_type": "pyout",
-       "prompt_number": 40,
-       "text": [
-        "(['2011-04-22'], ['21:06'], ['Grizzly'], [36])"
-       ]
-      }
-     ],
-     "prompt_number": 40
+     "source": [
+      "What other test cases did you define?"
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "***\n",
+      "**Aside: Test refactoring**\n",
+      "\n",
+      "You can see that there is already some reptition just among two test cases. Tests can be refactored to extract common code into helper functions just like production code can. A number of test frameworks exist to aid you with developing clear, succinct test harnesses. Python includes one called `unittest`.\n",
+      "***"
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "## Mean animals\n",
+      "\n",
+      "All that's left is to tie it all together. Fortunately, because we have focused on building small, cohesive, well-factored blocks of code up to this point, writing the final `mean_sightings` function should be straightforward."
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "## Exercise: Write the `mean_sightings` function and its tests\n",
+      "\n",
+      "This function should:\n",
+      "\n",
+      "* Accept the name of the file from which we are to read animal data\n",
+      "* Accept the name of the animal we are interested in catching in the filter\n",
+      "* Return the mean number of animals seen per sighting\n",
+      "* Be tested!"
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "## Results\n",
+      "\n",
+      "When you are done, your code should look something like this."
+     ]
     },
     {
      "cell_type": "code",
      "collapsed": false,
      "input": [
-      "def mean_animals(filename, species):\n",
-      "    d, t, a, c = read_file(filename)\n",
-      "    d, t, a, c = filter_animals(species, d, t, a, c)\n",
-      "    return calc_mean(c)"
+      "def mean_sightings(filename, species):\n",
+      "    dates, times, animals, counts = read_sightings_from_file(filename)\n",
+      "    dates, times, animals, counts = filter_animals(species, dates, times, animals, counts)\n",
+      "    return mean(counts)"
      ],
      "language": "python",
      "metadata": {},
      "outputs": [],
-     "prompt_number": 41
+     "prompt_number": 72
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "And your tests:"
+     ]
     },
     {
      "cell_type": "code",
      "collapsed": false,
      "input": [
-      "def test_mean_animals1():\n",
-      "    m = mean_animals('animals.txt', 'Elk')\n",
+      "def test_mean_elk_count_in_animals_txt_is_25_5():\n",
+      "    m = mean_sightings('animals.txt', 'Elk')\n",
       "    assert m == 25.5\n",
       "\n",
-      "def test_mean_animals2():\n",
-      "    m = mean_animals('animals.txt', 'Grizzly')\n",
+      "def test_mean_grizzly_count_in_animals_txt_is_36():\n",
+      "    m = mean_sightings('animals.txt', 'Grizzly')\n",
       "    assert m == 36"
      ],
      "language": "python",
      "metadata": {},
      "outputs": [],
-     "prompt_number": 42
+     "prompt_number": 75
     },
     {
      "cell_type": "code",
      "outputs": [
       {
        "html": [
-        "<div id=\"ipython_nose_237eee0b04e94196a101b786319e18c1\"></div>"
+        "<div id=\"ipython_nose_5c9bfbdef5e84275adfd02c8a2b010f0\"></div>"
+       ],
+       "output_type": "display_data"
+      },
+      {
+       "javascript": [
+        "document.ipython_nose_5c9bfbdef5e84275adfd02c8a2b010f0 = $(\"#ipython_nose_5c9bfbdef5e84275adfd02c8a2b010f0\");"
+       ],
+       "output_type": "display_data"
+      },
+      {
+       "javascript": [
+        "document.ipython_nose_5c9bfbdef5e84275adfd02c8a2b010f0.append($(\"<span>.</span>\"));"
+       ],
+       "output_type": "display_data"
+      },
+      {
+       "javascript": [
+        "document.ipython_nose_5c9bfbdef5e84275adfd02c8a2b010f0.append($(\"<span>.</span>\"));"
+       ],
+       "output_type": "display_data"
+      },
+      {
+       "javascript": [
+        "document.ipython_nose_5c9bfbdef5e84275adfd02c8a2b010f0.append($(\"<span>.</span>\"));"
+       ],
+       "output_type": "display_data"
+      },
+      {
+       "javascript": [
+        "document.ipython_nose_5c9bfbdef5e84275adfd02c8a2b010f0.append($(\"<span>.</span>\"));"
        ],
        "output_type": "display_data"
       },
       {
        "javascript": [
-        "document.ipython_nose_237eee0b04e94196a101b786319e18c1 = $(\"#ipython_nose_237eee0b04e94196a101b786319e18c1\");"
+        "document.ipython_nose_5c9bfbdef5e84275adfd02c8a2b010f0.append($(\"<span>.</span>\"));"
        ],
        "output_type": "display_data"
       },
       {
        "javascript": [
-        "document.ipython_nose_237eee0b04e94196a101b786319e18c1.append($(\"<span>.</span>\"));"
+        "document.ipython_nose_5c9bfbdef5e84275adfd02c8a2b010f0.append($(\"<span>.</span>\"));"
        ],
        "output_type": "display_data"
       },
       {
        "javascript": [
-        "document.ipython_nose_237eee0b04e94196a101b786319e18c1.append($(\"<span>.</span>\"));"
+        "document.ipython_nose_5c9bfbdef5e84275adfd02c8a2b010f0.append($(\"<span>.</span>\"));"
        ],
        "output_type": "display_data"
       },
       {
        "javascript": [
-        "document.ipython_nose_237eee0b04e94196a101b786319e18c1.append($(\"<span>.</span>\"));"
+        "document.ipython_nose_5c9bfbdef5e84275adfd02c8a2b010f0.append($(\"<span>.</span>\"));"
        ],
        "output_type": "display_data"
       },
       {
        "javascript": [
-        "document.ipython_nose_237eee0b04e94196a101b786319e18c1.append($(\"<span>.</span>\"));"
+        "document.ipython_nose_5c9bfbdef5e84275adfd02c8a2b010f0.append($(\"<span>.</span>\"));"
        ],
        "output_type": "display_data"
       },
       {
        "javascript": [
-        "document.ipython_nose_237eee0b04e94196a101b786319e18c1.append($(\"<span>.</span>\"));"
+        "document.ipython_nose_5c9bfbdef5e84275adfd02c8a2b010f0.append($(\"<span>.</span>\"));"
        ],
        "output_type": "display_data"
       },
       {
        "javascript": [
-        "document.ipython_nose_237eee0b04e94196a101b786319e18c1.append($(\"<span>.</span>\"));"
+        "document.ipython_nose_5c9bfbdef5e84275adfd02c8a2b010f0.append($(\"<span>.</span>\"));"
        ],
        "output_type": "display_data"
       },
       {
        "javascript": [
-        "document.ipython_nose_237eee0b04e94196a101b786319e18c1.append($(\"<span>.</span>\"));"
+        "document.ipython_nose_5c9bfbdef5e84275adfd02c8a2b010f0.append($(\"<span>.</span>\"));"
        ],
        "output_type": "display_data"
       },
       {
        "javascript": [
-        "document.ipython_nose_237eee0b04e94196a101b786319e18c1.append($(\"<span>.</span>\"));"
+        "document.ipython_nose_5c9bfbdef5e84275adfd02c8a2b010f0.append($(\"<span>.</span>\"));"
        ],
        "output_type": "display_data"
       },
       {
        "javascript": [
-        "delete document.ipython_nose_237eee0b04e94196a101b786319e18c1;"
+        "document.ipython_nose_5c9bfbdef5e84275adfd02c8a2b010f0.append($(\"<span>.</span>\"));"
+       ],
+       "output_type": "display_data"
+      },
+      {
+       "javascript": [
+        "delete document.ipython_nose_5c9bfbdef5e84275adfd02c8a2b010f0;"
        ],
        "output_type": "display_data"
       },
         "      <div class=\"nosebar pass rightmost\" style=\"width: 100%\">\n",
         "          &nbsp;\n",
         "      </div>\n",
-        "      8/8 tests passed\n",
+        "      14/14 tests passed\n",
         "    </div>\n",
         "    "
        ],
        "output_type": "pyout",
-       "prompt_number": 43,
+       "prompt_number": 76,
        "text": [
-        "8/8 tests passed\n"
+        "14/14 tests passed\n"
        ]
       }
      ],
-     "prompt_number": 43
+     "prompt_number": 76
     },
     {
-     "cell_type": "code",
-     "collapsed": false,
-     "input": [],
-     "language": "python",
+     "cell_type": "markdown",
      "metadata": {},
-     "outputs": []
+     "source": [
+      "You can have high confidence in the reliability of this function, because it is *composed* of calls to verified functions."
+     ]
     }
    ],
    "metadata": {}