From: Will Trimble Date: Wed, 31 Jul 2013 20:53:42 +0000 (-0400) Subject: Added jdblischak's testing content from 2013-06-chicago X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=a5ad05613def8bb19af053a432275b3c29c7d89a;p=swc-testing-nose.git Added jdblischak's testing content from 2013-06-chicago W. Trevor King: The provenance for this commit is unclear from the Git history alone, so I asked Will directly about Example2dnasequences.md. He responded with: On Fri, Nov 01, 2013 at 06:31:27PM -0400, Will Trimble wrote: > The file you ask about by name is the tail end of the file > https://github.com/jdblischak/boot-camps/blob/2013-06-chicago/09-testing/Readme.md > It looks like it was written 2013-06-13. > > It was a perhaps-misguided attempt to factorize the Fibionacci example from > the bio-inflected example of John's. > > This half-module on TDD exists in addition to three other edits of the > testing module: https://github.com/swcarpentry/bc/issues/106 (on > ecological, one basic stats, one Fibonacci that John replaced) --- a5ad05613def8bb19af053a432275b3c29c7d89a diff --cc python/testing/Example2dnasequences.md index 0000000,0000000..661b93e new file mode 100644 --- /dev/null +++ b/python/testing/Example2dnasequences.md @@@ -1,0 -1,0 +1,95 @@@ ++**Materials originally by John Blischak** ++ ++## A TDD Example ++ ++To illustrate TDD, let's return to the function you wrote yesterday, ++`calculate_gc`. We'll start from scratch and develop the function ++by meeting test specifications. ++ ++The beginning of the function is contained in the file `calculate_gc.py` ++in this directory. It currently takes one argument as input, but does ++nothing. ++ ++```python ++def calculate_gc(x): ++ ''' ++ Calculates the GC content of DNA sequence x. ++ ''' ++ pass ++``` ++ ++The tests that we must pass are contained in the file ++`test_calculate_gc.py`. We can run the tests using `nosetests`. ++ ++ nosetests -v test_calculate_gc.py ++ ++As expected, we fail all the tests! What is the bare minimum ++functionality we must add to pass the first test below? ++ ++```python ++def test_only_G_and_C(): ++ ''' ++ Sequence of only G's and C's has fraction 1.0 ++ ''' ++ fixture = 'GGCGCCGGC' ++ result = calculate_gc(fixture) ++ assert_equal(result, 1.0) ++``` ++ ++And the second test? ++ ++```python ++def test_half(): ++ ''' ++ Sequence with half G and C has fraction 0.5 ++ ''' ++ fixture = 'ATGC' ++ result = calculate_gc(fixture) ++ assert_equal(result, 0.5) ++``` ++ ++Test number three? ++ ++```python ++def test_lower_case(): ++ ''' ++ Sequence with lower case letters ++ ''' ++ fixture = 'atgc' ++ result = calculate_gc(fixture) ++ assert_equal(result, 0.5) ++``` ++ ++Test number four? ++ ++```python ++def test_not_DNA(): ++ ''' ++ Raise TypeError if not DNA ++ ''' ++ fixture = 'qwerty' ++ assert_raises(TypeError, calculate_gc, fixture) ++``` ++ ++Through this cycle of writing tests and modifying the function to pass ++the tests, we have developed a function that behaves exactly as we ++expect and nothing more. And the tests not only serve as documentation ++of what the function does, but can also be easily ran again if we made ++further modifications (regression tests). What would be the next test ++you would write for our function? ++ ++## Exercise: Test function that transcribes DNA to RNA ++ ++In the lesson yesterday on functions, `05-python-functions`, one exercise ++asked you to write a function to transcribe DNA to RNA. An example of ++that function is implemented in this directory in a file named ++`transcribe.py`. In that lesson, there were two tests to check your ++work: ++ ++```python ++transcribe('ATGC') == 'UACG' ++transcribe('ATGCAGTCAGTGCAGTCAGT') == 'UACGUCAGUCACGUCAGUCA' ++``` ++ ++Convert these to a proper test and place it the file `test_transcribe.py`. ++Next, add a few tests of your own and run the test suite with nosetests. diff --cc python/testing/Readme.md index 8d53b87,a23e081..a6f9985 --- a/python/testing/Readme.md +++ b/python/testing/Readme.md @@@ -50,7 -51,7 +50,7 @@@ answers we are interested in, data we w *Uncertainty Quantification* is the process of asking, "Given that our algorithm may not be deterministic, was our execution within acceptable error bounds?" This is particularly important for anything which uses --random numbers, eg Monte Carlo methods. ++random numbers, for example, Monte Carlo methods. # Where are tests? @@@ -276,11 -275,11 +276,11 @@@ teardown( # Nose: A Python Testing Framework -The testing framework we'll discuss today is called -[nose](https://nose.readthedocs.org/en/latest/). However, there are -several other testing frameworks available in most languages. Most +The testing framework we'll discuss today is called nose. However, there - are several other testing frameworks available in most language. Most ++are several other testing frameworks available in most languages. Most notably there is [JUnit](http://www.junit.org/) 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 @@@ -359,192 -358,94 +359,192 @@@ style was put forth most strongly by [K ## A TDD Example -To illustrate TDD, let's return to the function you wrote yesterday, -`calculate_gc`. We'll start from scratch and develop the function -by meeting test specifications. +Say you want to write a fib() function which generates values of the +Fibonacci sequence of given indexes. You would - of course - start by +writing the test, possibly testing a single value: + +```python +from nose.tools import assert_equal + +from pisa import fib -The beginning of the function is contained in the file `calculate_gc.py` -in this directory. It currently takes one argument as input, but does -nothing. +def test_fib1(): + obs = fib(2) + exp = 1 + assert_equal(obs, exp) +``` + +You would *then* go ahead and write the actual function: ```python -def calculate_gc(x): - ''' - Calculates the GC content of DNA sequence x. - ''' - pass +def fib(n): + # you snarky so-and-so + return 1 ``` -The tests that we must pass are contained in the file -`test_calculate_gc.py`. We can run the tests using `nosetests`. +And that is it right?! Well, not quite. This implementation fails for +most other values. Adding tests we see that: - nosetests -v test_calculate_gc.py +```python +def test_fib1(): + obs = fib(2) + exp = 1 + assert_equal(obs, exp) -As expected, we fail all the tests! What is the bare minimum -functionality we must add to pass the first test below? + +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 +initial values: ```python -def test_only_G_and_C(): - ''' - Sequence of only G's and C's has fraction 1.0 - ''' - fixture = 'GGCGCCGGC' - result = calculate_gc(fixture) - assert_equal(result, 1.0) +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) + + +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) ``` -And the second test? +At this point, we had better go ahead and try do the right thing... ```python -def test_half(): - ''' - Sequence with half G and C has fraction 0.5 - ''' - fixture = 'ATGC' - result = calculate_gc(fixture) - assert_equal(result, 0.5) +def fib(n): + # finally, some math + if n == 0 or n == 1: + return n + else: + return fib(n - 1) + fib(n - 2) ``` -Test number three? +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. ```python -def test_lower_case(): - ''' - Sequence with lower case letters - ''' - fixture = 'atgc' - result = calculate_gc(fixture) - assert_equal(result, 0.5) +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) ``` -Test number four? +This means that it is time to add the appropriate case to the function +itself: ```python -def test_not_DNA(): - ''' - Raise TypeError if not DNA - ''' - fixture = 'qwerty' - assert_raises(TypeError, calculate_gc, fixture) +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) ``` -Through this cycle of writing tests and modifying the function to pass -the tests, we have developed a function that behaves exactly as we -expect and nothing more. And the tests not only serve as documentation -of what the function does, but can also be easily ran again if we made -further modifications (regression tests). What would be the next test -you would write for our function? +# Quality Assurance Exercise + - Can you think of other tests to make for the fibonacci function? I promise there ++Can you think of other tests to make for the Fibonacci function? I promise there +are at least two. + +Implement one new test in test_fib.py, run nosetests, and if it fails, implement +a more robust function for that case. + +And thus - finally - we have a robust function together with working +tests! -## Exercise: Test function that transcribes DNA to RNA +# Exercise -In the lesson yesterday on functions, `05-python-functions`, one exercise -asked you to write a function to transcribe DNA to RNA. An example of -that function is implemented in this directory in a file named -`transcribe.py`. In that lesson, there were two tests to check your -work: +**The Problem:** In 2D or 3D, we have two points (p1 and p2) which +define a line segment. Additionally there exists experimental data which +can be anywhere in the domain. Find the data point which is closest to +the line segment. + +In the `close_line.py` file there are four different implementations +which all solve this problem. [You can read more about them +here.](http://inscight.org/2012/03/31/evolution_of_a_solution/) However, +there are no tests! Please write from scratch a `test_close_line.py` +file which tests the closest\_data\_to\_line() functions. + +*Hint:* you can use one implementation function to test another. Below +is some sample data to help you get started. + +![image](https://github.com/thehackerwithin/UofCSCBC2012/raw/scopz/5-Testing/evo_sol1.png) +> - ```python -transcribe('ATGC') == 'UACG' -transcribe('ATGCAGTCAGTGCAGTCAGT') == 'UACGUCAGUCACGUCAGUCA' +import numpy as np + +p1 = np.array([0.0, 0.0]) +p2 = np.array([1.0, 1.0]) +data = np.array([[0.3, 0.6], [0.25, 0.5], [1.0, 0.75]]) ``` -Convert these to a proper test and place it the file `test_transcribe.py`. -Next, add a few tests of your own and run the test suite with nosetests. diff --cc python/testing/example2/calculate_gc.py index 0000000,1838d4e..1838d4e mode 000000,100644..100644 --- a/python/testing/example2/calculate_gc.py +++ b/python/testing/example2/calculate_gc.py diff --cc python/testing/example2/mean.py index 0000000,7b419d6..7b419d6 mode 000000,100644..100644 --- a/python/testing/example2/mean.py +++ b/python/testing/example2/mean.py diff --cc python/testing/example2/test_calculate_gc.py index 0000000,b270735..b270735 mode 000000,100644..100644 --- a/python/testing/example2/test_calculate_gc.py +++ b/python/testing/example2/test_calculate_gc.py diff --cc python/testing/example2/test_mean.py index 0000000,44c3b7c..44c3b7c mode 000000,100644..100644 --- a/python/testing/example2/test_mean.py +++ b/python/testing/example2/test_mean.py diff --cc python/testing/example2/test_transcribe.py index 0000000,d9ac27f..d9ac27f mode 000000,100644..100644 --- a/python/testing/example2/test_transcribe.py +++ b/python/testing/example2/test_transcribe.py diff --cc python/testing/example2/transcribe.py index 0000000,488b9b7..488b9b7 mode 000000,100644..100644 --- a/python/testing/example2/transcribe.py +++ b/python/testing/example2/transcribe.py