summaryrefslogtreecommitdiff
path: root/tdd
diff options
context:
space:
mode:
authorPuneeth Chaganti2011-06-06 16:11:53 +0530
committerPuneeth Chaganti2011-06-06 16:11:53 +0530
commit985e57a26b0a5097924f3ebf469367fbb601e69c (patch)
tree3cb93c441da62b26a2f15b519ad3ffe95d8d10ee /tdd
parented63ecc5a98b4fd37d65bf0a6ca379248845a1a1 (diff)
downloadsees-985e57a26b0a5097924f3ebf469367fbb601e69c.tar.gz
sees-985e57a26b0a5097924f3ebf469367fbb601e69c.tar.bz2
sees-985e57a26b0a5097924f3ebf469367fbb601e69c.zip
tdd: Clean up notes.
Diffstat (limited to 'tdd')
-rw-r--r--tdd/tdd.rst908
1 files changed, 473 insertions, 435 deletions
diff --git a/tdd/tdd.rst b/tdd/tdd.rst
index a1f97c2..6347c47 100644
--- a/tdd/tdd.rst
+++ b/tdd/tdd.rst
@@ -2,85 +2,118 @@
Test Driven Development
=======================
-Fundamentals
+What is TDD?
============
-Test Driven Development, abbreviated as TDD is a method of software
-development which banks on the idea of writing test cases that fail for the
-code that doesn't even exist yet. The actual code is written later to pass
-the test and then refactored.
+Objectives
+----------
+At the end of this section, you will be able to:
+
+1. Write your code using the TDD paradigm.
+#. Use doctests to test your Python code.
+#. Use unittests to test your Python code.
+#. Use the nose module to test your code.
+
+
+Test Driven Development (TDD), as the name suggests, is a style or method
+of software development based on the idea of writing code, after writing
+tests for them. As the tests are written for code, that doesn't even exist
+yet, it is bound to fail. Actual code is later written, to pass the test
+and later on refactored.
+
+The basic steps of TDD are roughly as follows -
+
+1. Decide upon the feature to implement and the methodology of testing it.
+#. Write the tests for the feature decided upon.
+#. Just write enough code, so that the test can be run, but it fails.
+#. Improve the code, to just pass the test and at the same time passing all
+ previous tests.
+#. Run the tests to see, that all of them run successfully.
+#. Refactor the code we've just written -- optimize the algorithm, remove
+ duplication, add documentation, etc.
+#. Run the tests again, to see that all the tests still pass.
+#. Go back to 1.
First "Test"
============
-Writing a test is simple. Writing a failing test? It is much more simple.
-Let us consider a very simple program which returns the Greatest Common
-Divisor (GCD) of two numbers. Since the test cases for the code is written
-prior to the code itself, it is necessary to have a clear idea of the code
-units that our program will contain. Let us attempt to clearly define the
-code units in our case of a GCD program. Let our program contain one and
-only one function called gcd() which takes in two arguments as parameters.
-These arguments are the numbers for which GCD must be computed. The gcd()
-function returns a single value which is the GCD of the two arguments
-passed. So if we want to find out GCD of 44, 23, I will call my code unit
-as c = gcd(44, 23) where c will contain the GCD of those two numbers.
-
-Now we have defined our code units, how will we write tests? Before writing
-the test, a very fundamental question arises in our minds. How do tests
-look like? So let us answer this question first. Tests are nothing but a
-series of assertions which are either True or False depending on the
-expected behaviour of the code. We tell our tests whether our code unit
-asserts True or asserts False based on the expected behaviour of the code
-units. If we happen to run the tests now we are sure to get errors. Oh! But
-why? We don't even have the function gcd to call. The test code doesn't
-even compile! So what should we do now? So the idea is to first write the
-stubs for the code units before we start writing tests. This is necessary
-for two reasons. Firstly, by writing the stubs for the code units we will
-be able to correctly decide and fix on to the code units that we have
-planned to include in our program. We have a clear cut idea as to how our
-program is structured, how the tests must be written among other
-things. Secondly, the tests must at least compile and then fail! If the
-tests don't even compile, that doesn't mean the tests failed. It means
-it was a failure on the programmer's part. Let us define our stub::
+Now, that we have an overview of TDD, let's get down to writing our first
+test. Let us consider a very simple program which returns the Greatest
+Common Divisor (GCD) of two numbers.
- def gcd(a, b):
- pass
+Before being able to write our test, we need to have a clear idea of the
+code units our program will contain, so that we can test each of them.
+Let's first define the code units that our GCD program is going to have.
+
+Our program is going to contain only one function called ``gcd``, which
+will take in two arguments, and return a single value, which is the GCD of
+the two arguments. So if we want to find out GCD of 44, 23, we call the
+function, with the arguments 44 and 23.
+
+::
+
+ c = gcd(44, 23)
+
+c will contain the GCD of the two numbers.
+
+We have defined the code units in our program. So, we can go ahead and
+write tests for it.
+
+When adopting the TDD methodology, it is important to have a clear set of
+results defined for our code units i.e., for every given set of inputs as
+that we wish use as a test case, we must have, before hand, the exact
+outputs that are expected for those input test cases. We must not be
+running around looking for outputs for our test cases after we have the
+code ready, or even while we are writing the tests.
+
+Let one of our test cases be 48 and 64 as ``a`` and ``b``, respectively.
+For this test case we know that the expected output, the GCD, is 16. Let
+our second test case be 44 and 19 as ``a`` and ``b``, respectively. We know
+that their GCD is 1, and that is the expected output.
-This stub does nothing other than defining a new function called gcd
-which takes two parameters a and b for which the GCD must be
-calculated. The body of the function just contains Python's **pass**
-statement which means it does nothing, i.e. empty. We have our stub
-ready. One important thing we need to keep in mind when we adopt TDD
-methodology is that we need to have a clear set of results defined for
-our code units. To put it more clearly, for every given set of inputs
-as test case we must have, before hand, the exact outputs that are
-expected for those input test cases. If we don't have that we have
-failed in the first step of the TDD methodology itself. We must never
-run looking for outputs for our test cases after we have the code
-ready or even while writing tests. The expected outputs/behaviour must
-be in our hands before we start writing tests. Therefore let us define
-our test cases and the expected output for those inputs. Let one of
-our test cases be 48 and 64 as *a* and *b* respectively. For this test
-case we know that the GCD is 16. So that is the expected output. Let
-our second test case be 44 and 19 as *a* and *b* respectively. We know
-that their GCD is 1 by simple paper and pen calculation.
-
-Now we know what a test is? What are the ingredients required to write
-tests? So what else should we wait for? Let us write our first test!::
+But before writing one, we should What does a test look like? What does it
+do? Let's answer this question first.
+
+Tests are just a series of assertions which are either True or False
+depending on the expected behaviour of the code and the actual behaviour.
+Tests for out GCD function could be written as follows.
+
+::
tc1 = gcd(48, 64)
if tc1 != 16:
- print "Test failed for the case a=48 and b=64. Expected 16. Obtained %d instead." % tc1
+ print "Failed for a=48, b=64. Expected 16. Obtained %d instead." % tc1
exit(1)
tc2 = gcd(44, 19)
if tc2 != 1:
- print "Test failed for the case a=44 and b=19. Expected 1. Obtained %d instead." % tc2
+ print "Failed for a=44, b=19. Expected 1. Obtained %d instead." % tc2
exit(1)
print "All tests passed!"
-Let us put all these in a file and call this file **gcd.py**::
+The next step is to run the tests we have written, but if we run the tests
+now, the tests wouldn't even run. Why? We don't even have the function
+``gcd`` defined, for it to be called by the tests. The test code doesn't
+even run! What is the way out? We shall first write a very minimal
+definition (or a stub) of the function ``gcd``, so that it can be called by
+the tests.
+
+A minimal definition for the ``gcd`` function would look like this
+
+::
+
+ def gcd(a, b):
+ pass
+
+As you can see, the stub for ``gcd`` function does nothing, other than
+define the required function which takes the two arguments a and b. No
+action is done upon the arguments, passed to the function, yet. The
+function definition is empty and just contains the ``pass`` statement.
+
+Let us put all these in a file and call this file ``gcd.py``
+
+::
def gcd(a, b):
pass
@@ -88,307 +121,297 @@ Let us put all these in a file and call this file **gcd.py**::
if __name__ == '__main__':
tc1 = gcd(48, 64)
if tc1 != 16:
- print "Test failed for the case a=48 and b=64. Expected 16. Obtained %d instead." % tc1
+ print "Failed for a=48 and b=64. Expected 16. Obtained %d instead." % tc1
exit(1)
tc2 = gcd(44, 19)
if tc2 != 1:
- print "Test failed for the case a=44 and b=19. Expected 1. Obtained %d instead." % tc2
+ print "Failed for a=44 and b=19. Expected 1. Obtained %d instead." % tc2
exit(1)
print "All tests passed!"
-Note that we have introduced a new semantic which uses two new magic names
-in Python *__name__* and *__main__*. This is a very common idiom used in
-Python. Every Python code in a file can be run in two ways: Either as an
-independent stand-alone script or as a Python module which can be imported
-by other Python scripts or modules. When the idiom::
+Recall that, the condition ``__name__ == '__main__'`` becomes true, only
+when the python script is ran directly as a stand-alone script. So any code
+within this ``if`` block is executed only when the script is run as a
+stand-alone script and doesn't run when it is used as a module and
+imported.
- if __name__ == '__main__':
+Let us run our code as a stand-alone script.
-is used, the code within this if block is executed first when we run the
-Python file as a stand-alone script. In other words, when we run this
-python file as a stand-alone script the control of the program first starts
-from the code that is within this if block from which the control is
-transferred to other parts of the program or to other modules from
-here. This comes as an extremely handy feature especially when we want to
-test our modules individually. Now let us run our code as a stand-alone
-script.::
+::
$ python gcd.py
Traceback (most recent call last):
- File "gcd.py", line 7, in <module> print "Test failed for the case a=48 and b=64. Expected 16. Obtained %d instead." % tc1
+ File "gcd.py", line 7, in <module> print "Failed for a=48 and b=64. Expected 16. Obtained %d instead." % tc1
TypeError: %d format: a number is required, not NoneType
-Now we have our tests, the test cases and the code unit stub at
-hand. We also have the failing test. So we know for sure that we have
-cleared the first check point of TDD where the tests have failed. The
-failing tests also give a green signal for us to go ahead to our next
-check point i.e. to write the actual code in our code unit and make
-the test pass. So let us write the code for the gcd function by
-removing the **pass** control statement which had just created a gcd
-function stub for us.
-
-Most of us have learnt in high school math classes that the best and
-the easiest known algorithm to compute the gcd of two numbers was
-given to us 2300 years ago by a greek mathematician named Euclid. So
-let us use the Euclid's algorithm to compute the gcd of two numbers a
-and b::
+We now have our tests, the code unit stub, and a failing test. The next
+step is to write code, so that the test just passes.
- def gcd(a, b):
- if a == 0:
- return b
- while b != 0:
- if a > b:
- a = a - b
- else:
- b = b - a
- return a
-
-**Note**: If you are unaware of Euclidean algorithm to compute the gcd
-of two numbers please refer to it on wikipedia. It has a very detailed
-explanation of the algorithm and its proof of validity among other
-things.
-
-Now let us run our script which already has the tests written in it
-and see what happens::
+Let's us the algorithm given by a greek mathematician, 2300 years ago, the
+Euclidean algorithm, to compute the GCD of two numbers.
- $ python gcd.py
- All tests passed!
-
-Success! We managed to pass all the tests. But wasn't that code simple
-enough? Indeed it was. If you take a closer look at the code you will
-soon realize that the chain of subtraction operations can be replaced
-by a modulo operation i.e. taking remainders of the division between
-the two numbers since they are equivalent operations. Also modulo
-operation is far better than chain of subtractions because you will
-reduce much faster using modulo operation than the subtraction. For
-example if let us take 25, 5 as a and b in our example. If we write
-down the steps of the algorithm written above we have the following:
-
-Step 1: a = 25 b = 5: Since both a and b are not 0 and b is greater
-than a: b = 25 - 5 = 20
-Step 2: Since b is still not 0 and b is greater than a: b = 20 - 5 =
-15
-Step 3: Since b is still not 0 and b is greater than a: b = 15 - 5 =
-10
-Step 4: Since b is still not 0 and b is greater than a: b = 10 - 5 = 5
-Step 5: Since b is still not 0 and b is equal to a: b = 5 - 5 = 0
-Step 6: Since b is 0 the gcd is a = 5 which is returned
-
-If we adopt the modulo operation instead of subtraction and follow the
-steps:
-
-Step 1: a = 25 b = 5: Since both a and b are not 0 and b is greater
-than a: b = 25 % 5 = 0
-Step 2: Since b is 0 the gcd is a = 5 which is returned
-
-Wow! That was overwhelmingly lesser number of steps! So now we are
-convinced that if we replace the subtraction operation with the modulo
-operation our code performs much better. But if we think carefully we
-know that the modulo of a and b is less than b irrespective of how
-large the value of a is, including the case where a is already less
-than b. So we can eliminate that extra conditional **if** statement by
-just swapping the result of the modulo operation to the position of b
-and b to the position of a. This ensures that a is always greater than
-b and if not the swapping combined with modulo operation takes care of
-it. To exemplify it, if a = 5 and b = 25 then by swapping and
-performing modulo we have a = b = 25 and b = a % b = 5 % 25 = 5 and
-hence we proceed. So let us replace our original code with this new
-improved code we have come up with simple observations::
+**Note**: If you are unaware of Euclidean algorithm to compute the gcd of
+two numbers please refer to it on wikipedia. It has a very detailed
+explanation of the algorithm and its proof of validity among other things.
- def gcd(a, b):
- while b != 0:
- a, b = b, a % b
- return a
+The ``gcd`` stub function can be modified to the following function,
+
+::
+
+ def gcd(a, b):
+ if a == 0:
+ return b
+ while b != 0:
+ if a > b:
+ a = a - b
+ else:
+ b = b - a
+ return a
-Executing our script again we will see that all the tests pass. One
-final improvement we can think of which is not necessary in terms of
-efficiency but is certainly good to do keeping in mind the readability
-is that we can use the concept of recursion for the same
-algorithm. Without going into much detail this is how the code looks
-if we use a recursive approach::
+Now let us run our script which already has the tests written in it and see
+what happens
+
+::
+
+ $ python gcd.py
+ All tests passed!
+
+Success! We managed to write code, that passes all the tests. The code we
+wrote was simplistic, actually. If you take a closer look at the code you
+will soon realize that the chain of subtraction operations can be replaced
+by a modulo operation i.e. taking remainders of the division between the
+two numbers since they are equivalent operations. The modulo operation is
+far better, since it combines a lot of subtractions into one operation and
+the reduction is much faster. Take a few examples, and convince yourself
+that using the modulo operation is indeed faster than the subtraction
+method.
+
+::
+
+ def gcd1(a, b):
+ while b != 0 and a != 0:
+ if a > b:
+ a = a%b
+ else:
+ b = b%a
+ if a == 0:
+ return b
+ else:
+ return a
+
+But we know that the modulo of ``a%b`` is always less than b. So, if the
+condition ``a>b`` is True in this iteration, it will definitely be False in
+the next iteration. Also note that , and in the case when ``a < b``,
+``a%b`` is equal to ``a``. So, we could, essentially, get rid of the
+``if-else`` block and combine a swapping operation along with the modulo
+operation.
+
+::
+
+ def gcd(a, b):
+ while b != 0:
+ a, b = b, a % b
+ return a
+
+Let's run our tests again, and check that all the tests are passed, again.
+
+We could make one final
+improvement to our ``gcd`` function, which is strictly not necessary in
+terms of efficiency, but is definitely more readable and easy to
+understand. We could make our function a recursive function, as shown below
+
+::
def gcd(a, b):
if b == 0:
return a
return gcd(b, a%b)
-Much shorter and sweeter! And it passes all the tests! But there is
-one small problem yet. For the users of this function there is no way
-to determine how to use it, how many parameters it takes what it
-returns among other things. And same as well for those who read the
-code. So this function is not a very well written piece of code since
-it lacks documentation. So to make this function mode readable let us
-add the docstring for this function. Rewriting the function with the
-docstring looks like this::
+Much shorter and sweeter! We again run all the tests, and check that they
+are passed. It indeed does pass all the tests!
- def gcd(a, b):
- """Returns the Greatest Common Divisor of the two integers
- passed as arguments.
+But there is still one small problem. There is no way, for the users of
+this function, to determine how to use it, how many arguments it takes and
+what it returns, among other things. This is a handicap for the people
+reading your code, as well. This is well written function, with no
+documentation whatsoever.
- Args:
- a: an integer
- b: another integer
+Let's add a docstring as shown,
- Returns: Greatest Common Divisor of a and b
- """
- if b == 0:
- return a
- return gcd(b, a%b)
+::
+
+ def gcd(a, b):
+ """Returns the Greatest Common Divisor of the two integers
+ passed as arguments.
+
+ Args:
+ a: an integer
+ b: another integer
+
+ Returns: Greatest Common Divisor of a and b
+ """
+ if b == 0:
+ return a
+ return gcd(b, a%b)
Now we have refactored our code enough to make it well written piece
-of code. Let us move on.
-
-More realistic "Tests"
-======================
-
-Now we have successfully completed writing our first test, writing the
-relevant code and ensured the tests passed. We also refactored our
-code to perform better. With the knowledge of all these and some
-concepts and semantics like __main__ magic names we learnt we have
-come a long way with respect to writing tests. But our thirst is still
-unquenched! We want to do more and more tests! Not just write better
-code but also better tests! So let us keep building upon what we have
-learnt so far.
-
-Let us start writing tests for more realistic test cases. Generally
-tests are predetermined as said above, if not the software design in
-itself is flawed. The predetermined tests are stored along with the
-test code in some persistent format like in a database, a text file, a
-file of specific format like XML or in some other way. Let us continue
-with our example of GCD function. We will keep all our test cases in a
-text file, which is indeed persistent. Let us specify the format of
-the test data in our file as follows.
+of code. We have now successfully completed writing our first test, writing
+the relevant code, ensuring that the tests are passed and refactoring the
+code, until we are happy with the way it works and looks.
+
+Let's now build on what we have learnt so far and learn more about writing
+tests and improve our methodology of writing tests and simplify the process
+of writing them.
+
+Persistent Test Cases
+---------------------
+
+As already stated, tests should be predetermined and you should have your
+test cases and their outputs ready, even before you write the first line of
+code. This test data is repeatedly used in the process of writing code. So,
+it makes sense to have the test data to be stored in some persistent format
+like a database, a text file, a file of specific format like XML, etc.
+
+Let us modify the test code for the GCD function and save our test data in
+a text file. Let us decide upon the following format for the test data.
1. The file has multiple lines of test data.
2. Each line in this file corresponds to a single test case.
- 3. Each line consists of three comma separated coloumns:
+ 3. Each line consists of three comma separated values --
i. First two coloumns are the integers for which the GCD has to
be computed
ii. Third coloumn is the expected GCD to the preceding two
numbers.
-So how do we write our tests to use these test cases? Pretty simple, let
-us review the machinery required first.
+Now, how do we modify our tests to use this test data? We read the file
+line by line and parse it to obtain the required numbers whose GCD we wish
+to calculate, and the expected output. We can now call the ``gcd`` function
+with the correct arguments and verify the output with the expected output.
- 1. File reading: We already have learnt this in the modules on
- Basic Python.
- 2. Parsing the read data from the file: This just involves a using a
- **for** loop which iterates over the data line by line since we
- know that the file contains each test case as a sepate line which
- are equivalent to the file records and hence parse the data line
- by line as strings as we iterate over it and convert it to the
- required data type.
+Let us call our data file ``gcd_testcases.dat``
-Since we already have all the machinery required, let us proceed writing
-our test cases. We do not need not make any changes to the gcd
-function so we will just write down the test here. Let us call our
-data file gcd_testcases.dat::
+::
- if __name__ == '__main__':
- for line in open('gcd_testcases.dat'):
- values = line.split(', ')
- a = int(values[0])
- b = int(values[1])
- g = int(values[2])
+ if __name__ == '__main__':
+ for line in open('gcd_testcases.dat'):
+ values = line.split(', ')
+ a = int(values[0])
+ b = int(values[1])
+ g = int(values[2])
+
+ tc = gcd(a, b)
+ if tc != g:
+ print "Failed for a=%d and b=%d. Expected %d. Obtained %d instead." % (a, b, g, tc)
+ exit(1)
- tc = gcd(a, b)
- if tc != g:
- print "Test failed for the case a=%d and b=%d. Expected %d. Obtained %d instead." % (a, b, g, tc)
- exit(1)
+ print "All tests passed!"
- print "All tests passed!"
+When we execute the gcd.py script again we will notice that the code passes
+all the tests.
-When we execute the gcd.py script again we will notice that all the
-tests passed.
+Now, we have learnt how to write simple test cases and develop code based
+on these test cases. We shall now move on to learn to use some testing
+frameworks available for Python, which make some of the task easier.
-Python Testing Framework
-========================
+Python Testing Frameworks
+=========================
-Python provides two ways to test the code we have written. One of them
-is the unittest framework and the the other is the doctest module.
+Python provides two ways to test the code we have written. One of them is
+the unittest framework and the the other is the doctest module. To start
+with, let us discuss the doctest module.
doctest
~~~~~~~
-To start with let us discuss the doctest module. As we have already
-discussed a well written piece of code must always be accompanied by
-its documentation. For a function or a module we document them in their
-respective docstrings. In addition to this, we can also place the
-samples of using these functions or modules in the Python interactive
-interpreter in the docstrings. When we run the doctest module it picks
-up all such interactive session samples, executes them and determines
-if the documented piece of code runs as it is documented. Let us see
-how to write doctests for our gcd function::
-
- def gcd(a, b):
- """Returns the Greatest Common Divisor of the two integers
- passed as arguments.
-
- Args:
- a: an integer
- b: another integer
-
- Returns: Greatest Common Divisor of a and b
+As we have already discussed, a well written piece of code must always be
+accompanied by documentation. We document functions or modules using
+docstrings. In addition to writing a generic description of the function or
+the module, we can also add examples of using these functions in the
+interactive interpreter to the docstrings.
- >>> gcd(48, 64)
- 16
- >>> gcd(44, 19)
- 1
- """
- if b == 0:
- return a
- return gcd(b, a%b)
+Using the ``doctest`` module, we can pick up all such interactive session
+examples, execute them, and determine if the documented piece of code runs,
+as it was documented.
-This is all a doctest is. To explain it in more simple terms tests
-which are written as part of the docstrings are called as
-doctests. Now how do we use our doctest module to execute this
-tests. That is fairly straight forward as well. All we need to do is
-tell the doctest module to execute. Let us place this piece of code at
-the same place where we placed our tests earlier. So putting all these
-together we have our gcd.py module which looks as follows::
+The example below shows how to write ``doctests`` for our ``gcd`` function.
- def gcd(a, b):
- """Returns the Greatest Common Divisor of the two integers
- passed as arguments.
+::
- Args:
- a: an integer
- b: another integer
+ def gcd(a, b):
+ """Returns the Greatest Common Divisor of the two integers
+ passed as arguments.
+
+ Args:
+ a: an integer
+ b: another integer
+
+ Returns: Greatest Common Divisor of a and b
+
+ >>> gcd(48, 64)
+ 16
+ >>> gcd(44, 19)
+ 1
+ """
+ if b == 0:
+ return a
+ return gcd(b, a%b)
+
+That is all there is, to writing a ``doctest`` in Python. Now how do we use
+the ``doctest`` module to execute these tests. That too, is fairly straight
+forward. All we need to do is tell the doctest module to execute, using the
+``doctest.testmod`` function.
+
+Let us place this piece of code at the same place where we placed our tests
+earlier. So putting all these together we have our ``gcd.py`` module which
+looks as follows
+
+::
+
+ def gcd(a, b):
+ """Returns the Greatest Common Divisor of the two integers
+ passed as arguments.
+
+ Args:
+ a: an integer
+ b: another integer
+
+ Returns: Greatest Common Divisor of a and b
+
+ >>> gcd(48, 64)
+ 16
+ >>> gcd(44, 19)
+ 1
+ """
+ if b == 0:
+ return a
+ return gcd(b, a%b)
+
+ if __name__ == "__main__":
+ import doctest
+ doctest.testmod()
- Returns: Greatest Common Divisor of a and b
+The ``testmod`` function automatically picks up all the docstrings that
+have sample sessions from the interactive interpreter, executes them and
+compares the output with the results as specified in the sample sessions.
+If all the outputs match the expected outputs that have been documented, it
+doesn't say anything. It complains only if the results don't match as
+documented, expected results.
- >>> gcd(48, 64)
- 16
- >>> gcd(44, 19)
- 1
- """
- if b == 0:
- return a
- return gcd(b, a%b)
+When we execute this script as a stand-alone script we will get back the
+prompt with no messages which means all the tests passed
- if __name__ == "__main__":
- import doctest
- doctest.testmod()
+::
-All we need to do is import the doctest module that is part of the
-Python's standard library. Call the testmod() function in this
-module. This function automatically checks for all the docstrings that
-have sample sessions from the interactive interpreter, if they exist
-it executes them and compares the output with the results as specified
-in the sample sessions. It complains if the results don't match as
-documented. When we execute this script as a stand-alone script we
-will get back the prompt with no messages which means all the tests
-passed::
+ $ python gcd.py
+ $
- $ python gcd.py
- $
+If we further want to get a more detailed report of the tests that were
+executed we can run python with -v as the command line option to the script
-If we further want to get a more detailed report of the tests that
-were executed we can run python with -v as the command line option
-to the script::
+::
$ python gcd.py -v
Trying:
@@ -411,35 +434,38 @@ to the script::
**Note:** We can have the sample sessions as test cases as long as the
-outputs of the test cases do not contain any blank lines. In such
-cases we may have to use the exact string *<BLANKLINE>*
+outputs of the test cases do not contain any blank lines. In such cases we
+may have to use the exact string ``<BLANKLINE>``
-For the sake of illustrating a failing test case, let us assume that
-we made a small mistake in our code. Instead of returning **a** when b
-= 0 we typed it as return b when b = 0. So all the gcds returned will
-have the value of 0 in such a piece of code. The code looks as
-follows::
+For the sake of illustrating a failing test case, let us assume that we
+made a small mistake in our code. Instead of returning ``a`` when b = 0, we
+typed it as ``return b`` when b = 0. Now, all the GCDs returned will have
+the value 0. The code looks as follows
- def gcd(a, b):
- """Returns the Greatest Common Divisor of the two integers
- passed as arguments.
+::
- Args:
- a: an integer
- b: another integer
+ def gcd(a, b):
+ """Returns the Greatest Common Divisor of the two integers
+ passed as arguments.
+
+ Args:
+ a: an integer
+ b: another integer
+
+ Returns: Greatest Common Divisor of a and b
+
+ >>> gcd(48, 64)
+ 16
+ >>> gcd(44, 19)
+ 1
+ """
+ if b == 0:
+ return b
+ return gcd(b, a%b)
- Returns: Greatest Common Divisor of a and b
+Executing this code snippet without -v option to the script
- >>> gcd(48, 64)
- 16
- >>> gcd(44, 19)
- 1
- """
- if b == 0:
- return a
- return gcd(b, a%b)
-
-Executing this code snippet without -v option to the script::
+::
$ python gcd.py
**********************************************************************
@@ -465,154 +491,166 @@ Executing this code snippet without -v option to the script::
The output clearly complains that there were exactly two test cases
that failed. If we want a more verbose report we can pass -v option to
-the script. This is pretty much about the doctest module in
-Python. doctest is extremely useful when we want to test each Python
-function or module individually. For more information about the
-doctest module refer to the Python library reference on doctest[0].
+the script.
+
+This is all there is, to using the ``doctest`` module in Python.
+``doctest`` is extremely useful when we want to test each Python function
+or module individually.
+
+For more information about the doctest module refer to the Python library
+reference on doctest[0].
unittest framework
~~~~~~~~~~~~~~~~~~
-Not too far ahead we go we, we will start complaining that the doctest
-is not sufficient to write complicated tests especially when we want
-to automate our tests, write tests that need to test for more
-convoluted code pieces. For such scenarios Python provides a unittest
-framework. unittest framework provides methods to efficiently
-automate tests, setup and teardown functionalities which helps to
-setup the initializing code and data for executing the specific tests
-and cleanly shutting them down once the tests are executed and ways to
-aggregate tests into collections and better way of reporting the
-tests.
+We needn't go too far ahead, to start complaining that ``doctest`` is not
+sufficient to write complicated tests especially when we want to automate
+our tests, write tests that need to test for more convoluted code pieces.
+For such scenarios, Python provides a ``unittest`` framework.
+
+The ``unittest`` framework provides methods to efficiently automate tests,
+easily initialize code and data for executing the specific tests, cleanly
+shut them down once the tests are executed and easy ways of aggregating
+tests into collections and better way of reporting the tests.
Let us continue testing our gcd function in the Python module named
-gcd.py. To get ourselves started, the unittest framework expects us to
-subclass TestCase class in unittest module and place all our test code
-as methods of this class. We will begin the name of the test method
-with **test_** so that the test runner knows which methods are to be
-executed as tests. We will use the test cases supplied by
-gcd_testcases.dat. Lastly, to illustrate the way to test Python code
-as a module let create a new file called test_gcd.py following the
-same convention used to name the test methods. We will place our test
-code within test_gcd.py module. Our test code looks like this::
+``gcd.py``. The ``unittest`` framework expects us to subclass the
+``TestCase`` class in ``unittest`` module and place all our test code as
+methods of this class. The name of the test methods are expected to be
+started with ``test_``, so that the test runner knows which methods are to
+be executed as tests. We shall use the test cases supplied by
+``gcd_testcases.dat``.
+
+Lastly, to illustrate the way to test Python code as a module, let's create
+a new file called ``test_gcd.py`` following the same convention used to
+name the test methods, and place our test code in it.
+
+::
- import gcd
- import unittest
-
- class TestGcdFunction(unittest.TestCase):
-
- def setUp(self):
- self.test_file = open('gcd_testcases.dat')
- self.test_cases = []
- for line in self.test_file:
- values = line.split(', ')
- a = int(values[0])
- b = int(values[1])
- g = int(values[2])
-
- self.test_cases.append([a, b, g])
-
- def test_gcd(self):
- for case in self.test_cases:
- a = case[0]
- b = case[1]
- g = case[2]
- self.assertEqual(gcd.gcd(a, b), g)
-
- def tearDown(self):
- self.test_file.close()
- del self.test_cases
-
- if __name__ == '__main__':
- unittest.main()
+ import gcd
+ import unittest
+
+ class TestGcdFunction(unittest.TestCase):
+
+ def setUp(self):
+ self.test_file = open('gcd_testcases.dat')
+ self.test_cases = []
+ for line in self.test_file:
+ values = line.split(', ')
+ a = int(values[0])
+ b = int(values[1])
+ g = int(values[2])
+
+ self.test_cases.append([a, b, g])
+
+ def test_gcd(self):
+ for case in self.test_cases:
+ a = case[0]
+ b = case[1]
+ g = case[2]
+ self.assertEqual(gcd.gcd(a, b), g)
+
+ def tearDown(self):
+ self.test_file.close()
+ del self.test_cases
+
+ if __name__ == '__main__':
+ unittest.main()
-Please note that although we highly recommend to write a docstring for
-all the classes, functions and modules we have not done so to keep
-above code compact and we have left it as an exercise for the you to
-add them.
+Please note that although we highly recommend writing docstrings for all
+the classes, functions and modules, we have not done so, in this example to
+keep it compact. Adding suitable docstrings wherever required is left to
+you, as an exercise.
-Coming back to tests themselves, since we don't want to read this file
-into memory each time we run a separate test method, we will read all
-the data in the file into Python lists in the setUp method. The entire
-data file is kept in a list called test_cases which happens to be an
-attribute of the TestGCDFunction class. In the tearDown method of the
-class we will delete this attribute to free up the memory and close
-the opened file.
+It would be a waste, to read the test data file, each time we run a test
+method. So, in the setUp method, we read all the test data and store it
+into a list called test_cases, which is an attribute of the TestGCDFunction
+class. In the tearDown method of the class we will delete this attribute to
+free up the memory and close the opened file.
Our actual test code sits in the method which begins with the name
-**test_** as said earlier, the test_gcd method. Note that we import
-the gcd Python module we have written at the top of this test file and
-from this test method we call the gcd function within the gcd module
-to be tested with the each set of **a** and **b** values in the
-attribute test_cases. Once we execute the function we obtain the
+``test_``, as stated earlier, the ``test_gcd`` method. Note that, we import
+the ``gcd`` Python module we have written at the top of this test file and
+from this test method we call the ``gcd`` function within the ``gcd``
+module to be tested with the each set of ``a`` and ``b`` values
+``test_cases``. Once we execute the ``test_gcd`` function, we obtain the
result and compare it with the expected result as stored in the
-corresponding test_cases attribute using the assertEqual methods
-provided by our parent class TestCase in the unittest framework. There
-are several other assertion methods supplied by the unittest
-framework. For a more detailed information about this, refer to the
-unittest library reference at [1].
+corresponding ``test_cases`` attribute using the ``assertEqual`` method
+provided by ``TestCase`` class in the ``unittest`` framework. There are
+several other assertion methods supplied by the unittest framework. For a
+more detailed information about this, refer to the unittest library
+reference at [1]. This brings us to the end of our discussion of the
+``unittest`` framework.
nose
====
-Now we know almost all the varities of tests we may have to use to
-write self-sustained, automated tests for our code. There is one last
-thing that is left. However one question remains, how do we easily
-organize choose and run the tests that is scattered around several
-files?
-
-To further explain, the idea of placing tests with in the Python
-scripts and executing that test scripts themselves as stand-alone
-scripts works well as long as we have our code in a single Python file
-or as long as the tests for each script can be run separately. But in
-a more realistic software development scenario, often this is not the
-case. The code is spread around multiple Python modules and may be
-even across several Python packages.
-
-In such a such a scenario we wish we had a better tool to
-automatically aggregate these tests and execute them. Fortunately for
-us there exists a tool called nose. Although nose is not part of the
-standard Python distribution itself, it can be very easily installed
-by using easy_install command as follows::
+We have seen the ``unittest`` frame work and Python ``doctest`` module. .
+One problem however remains. It is not easy to organize, choose and run
+tests, when our code is scattered across multiple files. In the real world
+scenario, this is often the case.
+
+In such a such a scenario, a tool which can aggregate these tests
+automatically, and run them. The ``nose`` module, does precisely this, for
+us. It can aggregate ``unittests`` and ``doctests`` into a collection and
+run them. It also helps output the test-results and aggregate them in
+various formats.
+
+Although nose is not part of the standard Python distribution itself, it
+can be very easily installed by using easy_install command as follows
+
+::
$ easy_install nose
-Or download the nose package from [2], extracting the archive and
-running the command from the extracted directory::
+Or download the nose package from [2], extracting the archive and running
+the command from the extracted directory
+
+::
$ python setup.py install
-Now we have nose up and running, but how do we use it? It is very
-straight forward as well. We will use the command provided by nose
-called as nosetests. Run the following command in the top level
-directory of your code::
+Now we have nose up and running, but how do we use it? We shall use the
+``nosetests`` command provided by the ``nose`` module, in the top-level
+directory of our code.
+
+::
$ nosetests
-Thats all, nose automatically picks all the tests in all the
-directories and subdirectories in our code base and executes them
-all. However if we want to execute specific tests we can pass the test
-file names or the directories as arguments to nosetests command. For a
-detailed explanation about this, refer to [3]
+That's all! ``nose`` will now automatically pick all the tests in all the
+directories and subdirectories in our code base and execute them.
+
+However if we want to execute specific tests we can pass the test file
+names or the directories as arguments to nosetests command. For a detailed
+explanation about this, refer to [3]
Conclusion
==========
-Now we have all the trappings we want to write state-of-the art
-tests. To emphasize the same point again, any code which was written
-before writing the test and the testcases in hand is flawed by
-design. So it is recommended to follow the three step approach while
-writing code for any project as below:
+Now we have all the trappings we want to write state-of-the art tests. To
+emphasize the same point again, any code which was written before writing
+the test and the testcases in hand is flawed by design. So it is
+recommended to follow the three step approach while writing code for any
+project as below:
1. Write failing tests with testcases in hand.
2. Write the code to pass the tests.
3. Refactor the code for better performance.
-This approach is very famously known to the software development world
-as "Red-Green-Refactor" approach[4].
-
+This approach is very famously known to the software development world as
+"Red-Green-Refactor" approach[4].
[0] - http://docs.python.org/library/doctest.html
[1] - http://docs.python.org/library/unittest.html
[2] - http://pypi.python.org/pypi/nose/
[3] - http://somethingaboutorange.com/mrl/projects/nose/0.11.2/usage.html
[4] - http://en.wikipedia.org/wiki/Test-driven_development
+
+..
+ Local Variables:
+ mode: rst
+ indent-tabs-mode: nil
+ sentence-end-double-space: nil
+ fill-column: 75
+ End: