From: Jon Pipitone Date: Fri, 1 Mar 2013 23:17:30 +0000 (-0500) Subject: Adding exercises from the Software Carpentry 4.0 website X-Git-Url: http://git.tremily.us/?p=swc-testing-nose.git;a=commitdiff_plain;h=8bf8edca9507fc8c8bbaa88e92d5bbd37ad97b64 Adding exercises from the Software Carpentry 4.0 website W. Trevor King: I dropped everything from the original da170d3 except for the exercises/test.markdown addition. From Jon's initial pull request [1]: Here are a bunch of exercises, markdown-ized, that we used to have as part of the website for various topics. I wasn't sure where to put them exactly, so I've created a new top-level folder for them, and placed them there, flatly. I've named things so that the assets for a page of exercises have the same file name prefix (e.g. vc.markdown, and vc_planets.txt). [1]: https://github.com/swcarpentry/boot-camps/pull/34 --- 8bf8edca9507fc8c8bbaa88e92d5bbd37ad97b64 diff --git a/exercises/test.markdown b/exercises/test.markdown new file mode 100644 index 0000000..fb25fe1 --- /dev/null +++ b/exercises/test.markdown @@ -0,0 +1,137 @@ +The following exercises do not contain solutions. Yet. Instead, we will be +asking you to submit your solutions to these exercises and then we will post up +solutions at the start of next week. We encourage you to discuss your approaches +or solutions on the course forum! + +To submit your exercises, please create a `testing` folder in your personal +folder in the course repository. Place all of the code and files for theses +exercises in that folder and be sure to check it in. + + +## Exercise 1: Mileage + +The function 'convert_mileage' converts miles per gallon (US style) to liters +per 100 km (metric style): + +```python + gal_to_litre = 3.78541178 + mile_to_km = 1.609344 + + def convert_mileage(mpg): + '''Converts miles per gallon to liters per 100 km''' + litres_per_100_km = 100 / mpg / mile_to_km * gal_to_litre + return litres_per_100_km +``` + +Create a subdirectory in your version control directory called `testing`, then +copy this function into a file in that directory called `mileage.py`. Add more +code to that file to repeatedly ask the user for a mileage in miles per gallon, +and output the mileage in liters per 100 km, until the user enters the string +"`q`". You will need to use the `float()` function to convert from string to a +floating point number. Use the '`if __name__ == "__main__":`' trick to ensure +that the module can be imported without executing your testing code. + +1. Copy `mileage.py` to create `tryexcept.py` Add a try/except block to the new +program to display a helpful message instead of crashing when users enter +invalid input (such as the number "0" or the name of their favorite hockey +team). + +2. Reading the function again, you realize that accepting 0 or negative values +make no sense and should be reported as an error. Look at the exceptions defined +in the `exceptions` module (use the built-in `help(...)` or `dir(...)` +functions) and decide which of Python's built-in exceptions is most appropriate +to use for invalid input. Create a copy of 'tryexcept.py' called 'raiser.py' +that raises this exception; modify the main body of your program to catch it; +and add a comment inside the file explaining why you chose the exception you +did. (Note: you have to call this file `raiser.py`, not `raise.py` because +'import raise' is an error.  Can you see why?) + +3. [According to +Google](http://www.google.ca/search?q=20+miles+per+gallon+in+litres+per+100+km&gbv=1), +20 miles per gallon are equivalent to 11.7607292 liters per 100 km. Use these +values to write a unit test. Keep in mind that these floating values are subject +to truncation and rounding errors. Save the test case in a file called +`test_mileage.py` and run it using the `nosetests` command.  Note: +`test_mileage.py` should use '`from raiser import convert_mileage`' to get the +final version of your mileage conversion function. + +4. Now add a second test case, for 40 miles per gallon equivalent to 5.88036458 +liters per 100 km and run the tests again.  Unless you have already fixed the +error that was present in the initial function, your test should fail.  Find +and fix the error; submit your new function in a file called 'final_mileage.py'. + + +## Exercise 2: Testing Averages + +The results of a set of experiments are stored in a file, where the _i-th_ line +stores the results of the _i-th_ experiment as a comma-separated list of +integers. A student is assigned the task of finding the experiment with the +smallest average value. She writes the following code: + +```python + def avg_line(line): + values = line.split(',') + count = 0 + total = 0 + for value in values: + total += int(value) + count += 1 + return total / count + + def min_avg(file_name): + contents = open(file_name) + averages = [] + for (i, line) in enumerate(contents): + averages.append((avg_line(line), i)) + contents.close() + averages.sort() + min_avg, experiment_number = averages[0] + return experiment_number +``` + +1. Refactor `min_avg` so that it can be tested without depending on external +files. Submit your code in a file called `first_averages.py`. + +2. Write Nose test cases for both functions. Consider what should happen if the +file is empty. Submit your tests in a file called `test_first_averages.py`. +Note: you may assume for now that all input is well formatted, i.e., you do +_not_ have to worry about empty lines, lines containing the names of hockey +teams, etc. + +3. The given specification is ambiguous: what should the result be if two or +more experiments are tied for the minimum average? Copy 'first_averages.py' to +create a new file 'second_averages.py'; modify it to handle this case; add a +comment to the top explaining what rule you decided to use; and create a file +'test_second_averages.py' that tests your changes. + +4. Another student proposed an alternative implementation of the min_avg +function: + +```python + def min_avg(file_name): + contents = open(file_name).readlines() + min_avg = avg_line(contents[0]) + min_index = 0 + for (i,line) in enumerate(contents): + current_avg = avg_line(line) + if current_avg <= min_avg: + min_avg = current_avg + min_index = i + return min_index +``` + +This implementation also finds an experiment with the smallest average, but +possibly a different one than the your function. Modify your test cases so that +both your implementation and this one will pass. (Hint: use the 'in' operator.) + +5. One way to avoid the ambiguity of this specification is to define a +'min_avg_all' function instead, which returns a list with all the experiments +with the smallest average, and let the caller select one. Write tests for the +'min_avg_all' function, considering the following situations: an empty file, +exactly one experiment with minimum average, and more than one experiment with +minimum average. Keep in mind that in the last case, implementations could +return the list in different order. Write the tests the file "test_averages.py". +Use the same data as for the previous tests, if possible. You should use +variables to avoid code duplication. You don't need to implement the +'min_avg_all' function, but your test cases should be comprehensive enough to +serve as a specification for it.