From: Anthony Scopatz Date: Tue, 3 Apr 2012 04:04:44 +0000 (-0500) Subject: Fixed TTD example X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=29e29e46197073dcae9b6ca75a93c9625bb3a4a1;p=swc-testing-nose.git Fixed TTD example W. Trevor King: I dropped everything from the original 7cacdac except for the 5-Testing/ modification. Conflicts: 4-Debugging/tb_example.py --- diff --git a/5-Testing/Readme.md b/5-Testing/Readme.md index 606626a..43ed32d 100644 --- a/5-Testing/Readme.md +++ b/5-Testing/Readme.md @@ -10,6 +10,7 @@ Documentation](https://github.com/thehackerwithin/UofCSCBC2012/tree/master/6-Doc **Based on materials by Katy Huff, Rachel Slaybaugh, and Anthony Scopatz** +![image](http://memecreator.net/the-most-interesting-man-in-the-world/showimage.php/169/I-don't-always-test-my-code-But-when-I-do-I-do-it-in-production.jpg) # What is testing? Software testing is a process by which one or more expected behaviors @@ -124,20 +125,24 @@ Where, in a different file exists a test module: ```python import mean - def test_mean(): - assert mean([0, 0, 0, 0]) == 0 - assert mean([0, 200]) == 100 - assert mean([0, -200]) == -100 - assert mean([0]) == 0 +def test_mean(): + assert mean([0, 0, 0, 0]) == 0 + assert mean([0, 200]) == 100 + assert mean([0, -200]) == -100 + assert mean([0]) == 0 - def test_floating_mean(): - assert mean([1, 2]) == 1.5 +def test_floating_mean(): + assert mean([1, 2]) == 1.5 ``` # When should we test? -**ALWAYS!!!** +The three right answers are: + +- **ALWAYS!** +- **EARLY!** +- **OFTEN!** The longer answer is that testing either before or after your software is written will improve your code, but testing after your program is @@ -281,9 +286,9 @@ arguably attributed to inventing the testing framework. ## Where do nose tests live? -Nose tests are files that begin with Test-, Test\_, test-, or test\_. -Specifically, these satisfy the testMatch regular expression -[Tt]est[-\_]. (You can also teach nose to find tests by declaring them +Nose tests are files that begin with `Test-`, `Test_`, `test-`, or +`test_`. Specifically, these satisfy the testMatch regular expression +`[Tt]est[-_]`. (You can also teach nose to find tests by declaring them in the unittest.TestCase subclasses chat you create in your code. You can also create test functions which are not unittest.TestCase subclasses if they are named with the configured testMatch regular @@ -341,88 +346,172 @@ list of strings? # Test Driven Development -Some people develop code by writing the tests first. +Test driven development (TDD) is a philosophy whereby the developer +creates code by **writing the tests fist**. That is to say you write the +tests *before* writing the associated code! + +This is an iterative process whereby you write a test then write the +minimum amount code to make the test pass. If a new feature is needed, +another test is written and the code is expanded to meet this new use +case. This continues until the code does what is needed. + +TDD operates on the YAGNI principle (You Ain't Gonna Need It). People +who diligently follow TDD swear by its effectiveness. This development +style was put forth most strongly by [Kent Beck in +2002](http://www.amazon.com/Test-Driven-Development-By-Example/dp/0321146530). + +## A TDD Example + +Say you want to write a fib() function which generates values of the +Fibinacci sequence fof given indexes. You would - of course - start by +writing the test, possibly testing a single value: + +```python +from nose import assert_equal + +from pisa import fib + +def test_fib1(): + obs = fib(2) + exp = 1 + assert_equal(obs, exp) +``` + +You would *then* go ahead and write the actual function: + +```python +def fib(n): + # you snarky so-and-so + return 1 +``` + +And that is it right?! Well, not quite. This implementation fails for +most other values. Adding tests we see that: + +```python +def test_fib1(): + obs = fib(2) + exp = 1 + assert_equal(obs, exp) + + +def test_fib2(): + obs = fib(0) + exp = 0 + assert_equal(obs, exp) + + obs = fib(1) + exp = 1 + assert_equal(obs, exp) +``` + +This extra test now requires that we bother to implement at least the +intial values: + +```python +def fib(n): + # a little better + if n == 0 or n == 1: + return n + return 1 +``` + +However, this function still falls over for `2 < n`. Time for more +tests! + +```python +def test_fib1(): + obs = fib(2) + exp = 1 + assert_equal(obs, exp) -If you write your tests comprehensively enough, the expected behaviors -that you define in your tests will be the necessary and sufficient set -of behaviors your code must perform. Thus, if you write the tests first -and program until the tests pass, you will have written exactly enough -code to perform the behavior your want and no more. Furthermore, you -will have been forced to write your code in a modular enough way to make -testing easy now. This will translate into easier testing well into the -future. -### An example +def test_fib2(): + obs = fib(0) + exp = 0 + assert_equal(obs, exp) -The overlap method takes two rectangles (red and blue) and computes the -degree of overlap between them. Save it in overlap.py. A rectangle is -defined as a tuple of tuples: ((x\_lo,y\_lo),(x\_hi),(y\_hi)) + obs = fib(1) + exp = 1 + assert_equal(obs, exp) - def overlap(red, blue): - '''Return overlap between two rectangles, or None.''' - ((red_lo_x, red_lo_y), (red_hi_x, red_hi_y)) = red - ((blue_lo_x, blue_lo_y), (blue_hi_x, blue_hi_y)) = blue +def test_fib3(): + obs = fib(3) + exp = 2 + assert_equal(obs, exp) - if (red_lo_x >= blue_hi_x) or \ - (red_hi_x <= blue_lo_x) or \ - (red_lo_y >= blue_hi_x) or \ - (red_hi_y <= blue_lo_y): - return None + obs = fib(6) + exp = 8 + assert_equal(obs, exp) +``` - lo_x = max(red_lo_x, blue_lo_x) - lo_y = max(red_lo_y, blue_lo_y) - hi_x = min(red_hi_x, blue_hi_x) - hi_y = min(red_hi_y, blue_hi_y) - return ((lo_x, lo_y), (hi_x, hi_y)) +At this point, we had better go ahead and try do the right thing... -Now let's create a set of tests for this class. Before we do this, let's -think about *how* we might test this method. How should it work? +```python +def fib(n): + # finally, some math + if n == 0 or n == 1: + return n + else: + return fib(n - 1) + fib(n - 2) +``` - from overlap import overlap +Here it becomes very tempting to take an extended coffee break or +possibly a power lunch. But then you remember those pesky negative +numbers and floats. Perhaps the right thing to do here is to just be +undefined. - def test_empty_with_empty(): - rect = ((0, 0), (0, 0)) - assert overlap(rect, rect) == None +```python +def test_fib1(): + obs = fib(2) + exp = 1 + assert_equal(obs, exp) - def test_empty_with_unit(): - empty = ((0, 0), (0, 0)) - unit = ((0, 0), (1, 1)) - assert overlap(empty, unit) == None - def test_unit_with_unit(): - unit = ((0, 0), (1, 1)) - assert overlap(unit, unit) == unit +def test_fib2(): + obs = fib(0) + exp = 0 + assert_equal(obs, exp) - def test_partial_overlap(): - red = ((0, 3), (2, 5)) - blue = ((1, 0), (2, 4)) - assert overlap(red, blue) == ((1, 3), (2, 4)) + obs = fib(1) + exp = 1 + assert_equal(obs, exp) -Run your tests. - [rguy@infolab-33 ~/TestExample]$ nosetests - ...F - ====================================================================== - FAIL: test_overlap.test_partial_overlap - ---------------------------------------------------------------------- - Traceback (most recent call last): - File "/usr/lib/python2.6/site-packages/nose/case.py", line 183, in runTest - self.test(*self.arg) - File "/afs/ictp.it/home/r/rguy/TestExample/test_overlap.py", line 19, in test_partial_overlap - assert overlap(red, blue) == ((1, 3), (2, 4)) - AssertionError +def test_fib3(): + obs = fib(3) + exp = 2 + assert_equal(obs, exp) - ---------------------------------------------------------------------- - Ran 4 tests in 0.005s + obs = fib(6) + exp = 8 + assert_equal(obs, exp) - FAILED (failures=1) -Oh no! Something failed. The failure was on line in this test: +def test_fib3(): + obs = fib(13.37) + exp = NotImplemented + assert_equal(obs, exp) - def test_partial_overlap(): - red = ((0, 3), (2, 5)) - blue = ((1, 0), (2, 4)) - assert overlap(red, blue) == ((1, 3), (2, 4)) + obs = fib(-9000) + exp = NotImplemented + assert_equal(obs, exp) +``` + +This means that it is time to add the appropriate case to the funtion +itself: + +```python +def fib(n): + # sequence and you shall find + if n < 0 or int(n) != n: + return NotImplemented + elif n == 0 or n == 1: + return n + else: + return fib(n - 1) + fib(n - 2) +``` -Can you spot why it failed? Try to fix the method so all tests pass. +And thus - finally - we have a robust function together with working +tests! diff --git a/5-Testing/Readme.rst b/5-Testing/Readme.rst index 90216af..0f8075f 100644 --- a/5-Testing/Readme.rst +++ b/5-Testing/Readme.rst @@ -9,6 +9,9 @@ **Based on materials by Katy Huff, Rachel Slaybaugh, and Anthony Scopatz** +.. image:: http://memecreator.net/the-most-interesting-man-in-the-world/showimage.php/169/I-don%27t-always-test-my-code-But-when-I-do-I-do-it-in-production.jpg + + What is testing? ================ Software testing is a process by which one or more expected behaviors and @@ -120,7 +123,7 @@ Where, in a different file exists a test module: .. code-block:: python - import mean + import mean def test_mean(): assert mean([0, 0, 0, 0]) == 0 @@ -134,7 +137,11 @@ Where, in a different file exists a test module: When should we test? ==================== -**ALWAYS!!!** +The three right answers are: + +* **ALWAYS!** +* **EARLY!** +* **OFTEN!** The longer answer is that testing either before or after your software is written will improve your code, but testing after your program is used for @@ -265,8 +272,8 @@ in Java which can arguably attributed to inventing the testing framework. Where do nose tests live? ************************* -Nose tests are files that begin with Test-, Test_, test-, or test_. -Specifically, these satisfy the testMatch regular expression [Tt]est[-_]. +Nose tests are files that begin with ``Test-``, ``Test_``, ``test-``, or ``test_``. +Specifically, these satisfy the testMatch regular expression ``[Tt]est[-_]``. (You can also teach nose to find tests by declaring them in the unittest.TestCase subclasses chat you create in your code. You can also create test functions which are not unittest.TestCase subclasses if they are named with the configured @@ -322,92 +329,168 @@ a list of integers? What if you pass a list of strings? Test Driven Development ======================= -Some people develop code by writing the tests first. +Test driven development (TDD) is a philosophy whereby the developer creates code by +**writing the tests fist**. That is to say you write the tests *before* writing the +associated code! + +This is an iterative process whereby you write a test then write the minimum amount +code to make the test pass. If a new feature is needed, another test is written and +the code is expanded to meet this new use case. This continues until the code does +what is needed. + +TDD operates on the YAGNI principle (You Ain't Gonna Need It). People who diligently +follow TDD swear by its effectiveness. This development style was put forth most +strongly by `Kent Beck in 2002`_. + +.. _Kent Beck in 2002: http://www.amazon.com/Test-Driven-Development-By-Example/dp/0321146530 + +A TDD Example +************* +Say you want to write a fib() function which generates values of the +Fibinacci sequence fof given indexes. You would - of course - start +by writing the test, possibly testing a single value: + +.. code-block:: python + + from nose import assert_equal -If you write your tests comprehensively enough, the expected behaviors that you define in your tests will be the necessary and sufficient set of behaviors your code must perform. Thus, if you write the tests first and program until the tests pass, you will have written exactly enough code to perform the behavior your want and no more. Furthermore, you will have been forced to write your code in a modular enough way to make testing easy now. This will translate into easier testing well into the future. + from pisa import fib --------------------------------------------------------------------- -An example --------------------------------------------------------------------- -The overlap method takes two rectangles (red and blue) and computes the degree of overlap between them. Save it in overlap.py. A rectangle is defined as a tuple of tuples: ((x_lo,y_lo),(x_hi),(y_hi)) + def test_fib1(): + obs = fib(2) + exp = 1 + assert_equal(obs, exp) -:: +You would *then* go ahead and write the actual function: + +.. code-block:: python - def overlap(red, blue): - '''Return overlap between two rectangles, or None.''' + def fib(n): + # you snarky so-and-so + return 1 - ((red_lo_x, red_lo_y), (red_hi_x, red_hi_y)) = red - ((blue_lo_x, blue_lo_y), (blue_hi_x, blue_hi_y)) = blue +And that is it right?! Well, not quite. This implementation fails for +most other values. Adding tests we see that: - if (red_lo_x >= blue_hi_x) or \ - (red_hi_x <= blue_lo_x) or \ - (red_lo_y >= blue_hi_x) or \ - (red_hi_y <= blue_lo_y): - return None +.. code-block:: python - lo_x = max(red_lo_x, blue_lo_x) - lo_y = max(red_lo_y, blue_lo_y) - hi_x = min(red_hi_x, blue_hi_x) - hi_y = min(red_hi_y, blue_hi_y) - return ((lo_x, lo_y), (hi_x, hi_y)) + def test_fib1(): + obs = fib(2) + exp = 1 + assert_equal(obs, exp) -Now let's create a set of tests for this class. Before we do this, let's think about *how* we might test this method. How should it work? + def test_fib2(): + obs = fib(0) + exp = 0 + assert_equal(obs, exp) + obs = fib(1) + exp = 1 + assert_equal(obs, exp) -:: +This extra test now requires that we bother to implement at least the intial values: - from overlap import overlap +.. code-block:: python - def test_empty_with_empty(): - rect = ((0, 0), (0, 0)) - assert overlap(rect, rect) == None + def fib(n): + # a little better + if n == 0 or n == 1: + return n + return 1 - def test_empty_with_unit(): - empty = ((0, 0), (0, 0)) - unit = ((0, 0), (1, 1)) - assert overlap(empty, unit) == None +However, this function still falls over for ``2 < n``. Time for more tests! - def test_unit_with_unit(): - unit = ((0, 0), (1, 1)) - assert overlap(unit, unit) == unit +.. code-block:: python - def test_partial_overlap(): - red = ((0, 3), (2, 5)) - blue = ((1, 0), (2, 4)) - assert overlap(red, blue) == ((1, 3), (2, 4)) + def test_fib1(): + obs = fib(2) + exp = 1 + assert_equal(obs, exp) -Run your tests. + def test_fib2(): + obs = fib(0) + exp = 0 + assert_equal(obs, exp) -:: + obs = fib(1) + exp = 1 + assert_equal(obs, exp) - [rguy@infolab-33 ~/TestExample]$ nosetests - ...F - ====================================================================== - FAIL: test_overlap.test_partial_overlap - ---------------------------------------------------------------------- - Traceback (most recent call last): - File "/usr/lib/python2.6/site-packages/nose/case.py", line 183, in runTest - self.test(*self.arg) - File "/afs/ictp.it/home/r/rguy/TestExample/test_overlap.py", line 19, in test_partial_overlap - assert overlap(red, blue) == ((1, 3), (2, 4)) - AssertionError - ---------------------------------------------------------------------- - Ran 4 tests in 0.005s + def test_fib3(): + obs = fib(3) + exp = 2 + assert_equal(obs, exp) - FAILED (failures=1) + obs = fib(6) + exp = 8 + assert_equal(obs, exp) +At this point, we had better go ahead and try do the right thing... -Oh no! Something failed. The failure was on line in this test: +.. code-block:: python -:: + def fib(n): + # finally, some math + if n == 0 or n == 1: + return n + else: + return fib(n - 1) + fib(n - 2) - def test_partial_overlap(): - red = ((0, 3), (2, 5)) - blue = ((1, 0), (2, 4)) - assert overlap(red, blue) == ((1, 3), (2, 4)) +Here it becomes very tempting to take an extended coffee break or possibly a +power lunch. But then you remember those pesky negative numbers and floats. +Perhaps the right thing to do here is to just be undefined. + +.. code-block:: python + + def test_fib1(): + obs = fib(2) + exp = 1 + assert_equal(obs, exp) + + + def test_fib2(): + obs = fib(0) + exp = 0 + assert_equal(obs, exp) + + obs = fib(1) + exp = 1 + assert_equal(obs, exp) + + + def test_fib3(): + obs = fib(3) + exp = 2 + assert_equal(obs, exp) + + obs = fib(6) + exp = 8 + assert_equal(obs, exp) + + + def test_fib3(): + obs = fib(13.37) + exp = NotImplemented + assert_equal(obs, exp) + + obs = fib(-9000) + exp = NotImplemented + assert_equal(obs, exp) + +This means that it is time to add the appropriate case to the funtion itself: + +.. code-block:: python + def fib(n): + # sequence and you shall find + if n < 0 or int(n) != n: + return NotImplemented + elif n == 0 or n == 1: + return n + else: + return fib(n - 1) + fib(n - 2) -Can you spot why it failed? Try to fix the method so all tests pass. +And thus - finally - we have a robust function together with working tests!