From 81887ddc303c31f49ffc21566a69af5a2ff76d99 Mon Sep 17 00:00:00 2001 From: Jon Speicher Date: Sat, 27 Jul 2013 20:25:59 -0400 Subject: [PATCH] Add practice section and examples --- .../sw_engineering/SoftwareEngineering.ipynb | 373 +++++++++++++----- 1 file changed, 285 insertions(+), 88 deletions(-) diff --git a/python/sw_engineering/SoftwareEngineering.ipynb b/python/sw_engineering/SoftwareEngineering.ipynb index de26100..b2ca6ef 100644 --- a/python/sw_engineering/SoftwareEngineering.ipynb +++ b/python/sw_engineering/SoftwareEngineering.ipynb @@ -22,7 +22,8 @@ "* Testing\n", "* Nose\n", "* Test-driven development\n", - "* Exceptions" + "* Exceptions\n", + "* Practice" ] }, { @@ -2742,34 +2743,74 @@ "***" ] }, + { + "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", @@ -2792,25 +2833,85 @@ ] } ], - "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", @@ -2823,55 +2924,79 @@ "outputs": [ { "html": [ - "
" + "
" + ], + "output_type": "display_data" + }, + { + "javascript": [ + "document.ipython_nose_421c36436bd1424eaeea6cc04f73853f = $(\"#ipython_nose_421c36436bd1424eaeea6cc04f73853f\");" + ], + "output_type": "display_data" + }, + { + "javascript": [ + "document.ipython_nose_421c36436bd1424eaeea6cc04f73853f.append($(\".\"));" + ], + "output_type": "display_data" + }, + { + "javascript": [ + "document.ipython_nose_421c36436bd1424eaeea6cc04f73853f.append($(\".\"));" ], "output_type": "display_data" }, { "javascript": [ - "document.ipython_nose_d965cb31197d482394af98b11319a703 = $(\"#ipython_nose_d965cb31197d482394af98b11319a703\");" + "document.ipython_nose_421c36436bd1424eaeea6cc04f73853f.append($(\".\"));" ], "output_type": "display_data" }, { "javascript": [ - "document.ipython_nose_d965cb31197d482394af98b11319a703.append($(\".\"));" + "document.ipython_nose_421c36436bd1424eaeea6cc04f73853f.append($(\".\"));" ], "output_type": "display_data" }, { "javascript": [ - "document.ipython_nose_d965cb31197d482394af98b11319a703.append($(\".\"));" + "document.ipython_nose_421c36436bd1424eaeea6cc04f73853f.append($(\".\"));" ], "output_type": "display_data" }, { "javascript": [ - "document.ipython_nose_d965cb31197d482394af98b11319a703.append($(\".\"));" + "document.ipython_nose_421c36436bd1424eaeea6cc04f73853f.append($(\".\"));" ], "output_type": "display_data" }, { "javascript": [ - "document.ipython_nose_d965cb31197d482394af98b11319a703.append($(\".\"));" + "document.ipython_nose_421c36436bd1424eaeea6cc04f73853f.append($(\".\"));" ], "output_type": "display_data" }, { "javascript": [ - "document.ipython_nose_d965cb31197d482394af98b11319a703.append($(\".\"));" + "document.ipython_nose_421c36436bd1424eaeea6cc04f73853f.append($(\".\"));" ], "output_type": "display_data" }, { "javascript": [ - "document.ipython_nose_d965cb31197d482394af98b11319a703.append($(\".\"));" + "document.ipython_nose_421c36436bd1424eaeea6cc04f73853f.append($(\".\"));" ], "output_type": "display_data" }, { "javascript": [ - "delete document.ipython_nose_d965cb31197d482394af98b11319a703;" + "document.ipython_nose_421c36436bd1424eaeea6cc04f73853f.append($(\".\"));" + ], + "output_type": "display_data" + }, + { + "javascript": [ + "delete document.ipython_nose_421c36436bd1424eaeea6cc04f73853f;" ], "output_type": "display_data" }, @@ -2982,69 +3107,106 @@ "
\n", "  \n", "
\n", - " 6/6 tests passed\n", + " 10/10 tests passed\n", " \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", @@ -3057,67 +3219,103 @@ "outputs": [ { "html": [ - "
" + "
" + ], + "output_type": "display_data" + }, + { + "javascript": [ + "document.ipython_nose_5c9bfbdef5e84275adfd02c8a2b010f0 = $(\"#ipython_nose_5c9bfbdef5e84275adfd02c8a2b010f0\");" + ], + "output_type": "display_data" + }, + { + "javascript": [ + "document.ipython_nose_5c9bfbdef5e84275adfd02c8a2b010f0.append($(\".\"));" + ], + "output_type": "display_data" + }, + { + "javascript": [ + "document.ipython_nose_5c9bfbdef5e84275adfd02c8a2b010f0.append($(\".\"));" + ], + "output_type": "display_data" + }, + { + "javascript": [ + "document.ipython_nose_5c9bfbdef5e84275adfd02c8a2b010f0.append($(\".\"));" + ], + "output_type": "display_data" + }, + { + "javascript": [ + "document.ipython_nose_5c9bfbdef5e84275adfd02c8a2b010f0.append($(\".\"));" ], "output_type": "display_data" }, { "javascript": [ - "document.ipython_nose_237eee0b04e94196a101b786319e18c1 = $(\"#ipython_nose_237eee0b04e94196a101b786319e18c1\");" + "document.ipython_nose_5c9bfbdef5e84275adfd02c8a2b010f0.append($(\".\"));" ], "output_type": "display_data" }, { "javascript": [ - "document.ipython_nose_237eee0b04e94196a101b786319e18c1.append($(\".\"));" + "document.ipython_nose_5c9bfbdef5e84275adfd02c8a2b010f0.append($(\".\"));" ], "output_type": "display_data" }, { "javascript": [ - "document.ipython_nose_237eee0b04e94196a101b786319e18c1.append($(\".\"));" + "document.ipython_nose_5c9bfbdef5e84275adfd02c8a2b010f0.append($(\".\"));" ], "output_type": "display_data" }, { "javascript": [ - "document.ipython_nose_237eee0b04e94196a101b786319e18c1.append($(\".\"));" + "document.ipython_nose_5c9bfbdef5e84275adfd02c8a2b010f0.append($(\".\"));" ], "output_type": "display_data" }, { "javascript": [ - "document.ipython_nose_237eee0b04e94196a101b786319e18c1.append($(\".\"));" + "document.ipython_nose_5c9bfbdef5e84275adfd02c8a2b010f0.append($(\".\"));" ], "output_type": "display_data" }, { "javascript": [ - "document.ipython_nose_237eee0b04e94196a101b786319e18c1.append($(\".\"));" + "document.ipython_nose_5c9bfbdef5e84275adfd02c8a2b010f0.append($(\".\"));" ], "output_type": "display_data" }, { "javascript": [ - "document.ipython_nose_237eee0b04e94196a101b786319e18c1.append($(\".\"));" + "document.ipython_nose_5c9bfbdef5e84275adfd02c8a2b010f0.append($(\".\"));" ], "output_type": "display_data" }, { "javascript": [ - "document.ipython_nose_237eee0b04e94196a101b786319e18c1.append($(\".\"));" + "document.ipython_nose_5c9bfbdef5e84275adfd02c8a2b010f0.append($(\".\"));" ], "output_type": "display_data" }, { "javascript": [ - "document.ipython_nose_237eee0b04e94196a101b786319e18c1.append($(\".\"));" + "document.ipython_nose_5c9bfbdef5e84275adfd02c8a2b010f0.append($(\".\"));" ], "output_type": "display_data" }, { "javascript": [ - "delete document.ipython_nose_237eee0b04e94196a101b786319e18c1;" + "document.ipython_nose_5c9bfbdef5e84275adfd02c8a2b010f0.append($(\".\"));" + ], + "output_type": "display_data" + }, + { + "javascript": [ + "delete document.ipython_nose_5c9bfbdef5e84275adfd02c8a2b010f0;" ], "output_type": "display_data" }, @@ -3228,26 +3426,25 @@ "
\n", "  \n", "
\n", - " 8/8 tests passed\n", + " 14/14 tests passed\n", " \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": {} -- 2.26.2