tdd/tdd.rst
changeset 110 4e7b98636b58
parent 109 0afd25eadf41
child 112 0b01bb6ea6b8
equal deleted inserted replaced
109:0afd25eadf41 110:4e7b98636b58
    40 it was a failure on the programmer's part. Let us define our stub::
    40 it was a failure on the programmer's part. Let us define our stub::
    41 
    41 
    42   def gcd(a, b):
    42   def gcd(a, b):
    43       pass
    43       pass
    44 
    44 
    45 This stub does nothing other than defining a new function called gcd which
    45 This stub does nothing other than defining a new function called gcd
    46 takes two parameters a and b for which the GCD must be calculated. The body
    46 which takes two parameters a and b for which the GCD must be
    47 of the function just contains pass which means it does nothing, i.e. empty.
    47 calculated. The body of the function just contains Python's **pass**
    48 We have our stub ready. One important thing we need to keep in mind when
    48 statement which means it does nothing, i.e. empty. We have our stub
    49 we adopt TDD methodology is that we need to have a clear set of results
    49 ready. One important thing we need to keep in mind when we adopt TDD
    50 defined for our code units. To put it more clearly, for every given set of
    50 methodology is that we need to have a clear set of results defined for
    51 inputs as test case we must have, before hand, the exact outputs that are
    51 our code units. To put it more clearly, for every given set of inputs
    52 expected for those input test cases. If we don't have that we have failed
    52 as test case we must have, before hand, the exact outputs that are
    53 in the first step of the TDD methodology itself. We must never run for
    53 expected for those input test cases. If we don't have that we have
    54 outputs for our test cases after we have the code ready or even while
    54 failed in the first step of the TDD methodology itself. We must never
    55 writing tests. The expected outputs/behaviour must be in our hands before
    55 run looking for outputs for our test cases after we have the code
    56 we start writing tests. Therefore let us define our test cases and the
    56 ready or even while writing tests. The expected outputs/behaviour must
    57 expected output for those inputs. Let one of our test cases be 48 and 64
    57 be in our hands before we start writing tests. Therefore let us define
    58 as *a* and *b* respectively. For this test case we know that the GCD is
    58 our test cases and the expected output for those inputs. Let one of
    59 16. So that is the expected output. Let our second test case be 44 and
    59 our test cases be 48 and 64 as *a* and *b* respectively. For this test
    60 19 as *a* and *b* respectively. We know that their GCD is 1 by simple paper
    60 case we know that the GCD is 16. So that is the expected output. Let
    61 and pen calculation.
    61 our second test case be 44 and 19 as *a* and *b* respectively. We know
       
    62 that their GCD is 1 by simple paper and pen calculation.
    62 
    63 
    63 Now we know what a test is? What are the ingredients required to write
    64 Now we know what a test is? What are the ingredients required to write
    64 tests? So what else should we wait for? Let us write our first test!::
    65 tests? So what else should we wait for? Let us write our first test!::
    65 
    66 
    66   tc1 = gcd(48, 64)
    67   tc1 = gcd(48, 64)
    67   if tc1 != 16:
    68   if tc1 != 16:
    68       print "Test failed for the case a=48 and b=64. Expected 16. Obtained
    69       print "Test failed for the case a=48 and b=64. Expected 16. Obtained %d instead." % tc1
    69           %d instead." % tc1
       
    70       exit(1)
    70       exit(1)
    71   
    71   
    72   tc2 = gcd(44, 19)
    72   tc2 = gcd(44, 19)
    73   if tc2 != 1:
    73   if tc2 != 1:
    74       print "Test failed for the case a=44 and b=19. Expected 1. Obtained
    74       print "Test failed for the case a=44 and b=19. Expected 1. Obtained %d instead." % tc2
    75           %d instead." % tc2
       
    76       exit(1)
    75       exit(1)
    77 
    76 
    78   print "All tests passed!"
    77   print "All tests passed!"
       
    78 
       
    79 Let us put all these in a file and call this file **gcd.py**::
       
    80 
       
    81   def gcd(a, b):
       
    82       pass
       
    83 
       
    84   if __name__ == '__main__':
       
    85       tc1 = gcd(48, 64)
       
    86       if tc1 != 16:
       
    87           print "Test failed for the case a=48 and b=64. Expected 16. Obtained %d instead." % tc1
       
    88           exit(1)
       
    89 
       
    90       tc2 = gcd(44, 19)
       
    91       if tc2 != 1:
       
    92           print "Test failed for the case a=44 and b=19. Expected 1. Obtained %d instead." % tc2
       
    93           exit(1)
       
    94 
       
    95       print "All tests passed!"
       
    96 
       
    97 Note that we have introduced a new semantic which uses two new magic names
       
    98 in Python *__name__* and *__main__*. This is a very common idiom used in
       
    99 Python. Every Python code in a file can be run in two ways: Either as an
       
   100 independent stand-alone script or as a Python module which can be imported
       
   101 by other Python scripts or modules. When the idiom::
       
   102 
       
   103   if __name__ == '__main__':
       
   104 
       
   105 is used, the code within this if block is executed first when we run the
       
   106 Python file as a stand-alone script. In other words, when we run this
       
   107 python file as a stand-alone script the control of the program first starts
       
   108 from the code that is within this if block from which the control is
       
   109 transferred to other parts of the program or to other modules from
       
   110 here. This comes as an extremely handy feature especially when we want to
       
   111 test our modules individually. Now let us run our code as a stand-alone
       
   112 script.::
       
   113 
       
   114   madhu@madhu:~/Desktop$ python gcd.py
       
   115   Traceback (most recent call last):
       
   116     File "gcd.py", line 7, in <module> print "Test failed for the case a=48 and b=64. Expected 16. Obtained %d instead." % tc1
       
   117   TypeError: %d format: a number is required, not NoneType
       
   118 
       
   119 Now we have our tests, the test cases and the code unit stub at
       
   120 hand. We also have the failing test. So we know for sure that we have
       
   121 cleared the first check point of TDD where the tests have failed. The
       
   122 failing tests also give a green signal for us to go ahead to our next
       
   123 check point i.e. to write the actual code in our code unit and make
       
   124 the test pass. So let us write the code for the gcd function by
       
   125 removing the **pass** control statement which had just created a gcd
       
   126 function stub for us.
       
   127 
       
   128 Most of us have learnt in high school math classes that the best and
       
   129 the easiest known algorithm to compute the gcd of two numbers was
       
   130 given to us 2300 years ago by a greek mathematician named Euclid. So
       
   131 let us use the Euclid's algorithm to compute the gcd of two numbers a
       
   132 and b::
       
   133 
       
   134   def gcd(a, b):
       
   135       if a == 0:
       
   136           return b
       
   137       while b != 0:
       
   138           if a > b:
       
   139               a = a - b
       
   140           else:
       
   141               b = b - a
       
   142       return a
       
   143 
       
   144 **Note**: If you are unaware of Euclidean algorithm to compute the gcd
       
   145 of two numbers please refer to it on wikipedia. It has a very detailed
       
   146 explanation of the algorithm and its proof of validity among other
       
   147 things.
       
   148 
       
   149 Now let us run our script which already has the tests written in it
       
   150 and see what happens::
       
   151 
       
   152   madhu@madhu:/media/python/sttp/tdd$ python gcd.py
       
   153   All tests passed!
       
   154 
       
   155 Success! We managed to pass all the tests. But wasn't that code simple
       
   156 enough? Indeed it was. If you take a closer look at the code you will
       
   157 soon realize that the chain of subtraction operations can be replaced
       
   158 by a modulo operation i.e. taking remainders of the division between
       
   159 the two numbers since they are equivalent operations. Also modulo
       
   160 operation is far better than chain of subtractions because you will
       
   161 reduce much faster using modulo operation than the subtraction. For
       
   162 example if let us take 25, 5 as a and b in our example. If we write
       
   163 down the steps of the algorithm written above we have the following:
       
   164 
       
   165 Step 1: a = 25 b = 5: Since both a and b are not 0 and b is greater
       
   166 than a: b = 25 - 5 = 20
       
   167 Step 2: Since b is still not 0 and b is greater than a: b = 20 - 5 =
       
   168 15
       
   169 Step 3: Since b is still not 0 and b is greater than a: b = 15 - 5 =
       
   170 10
       
   171 Step 4: Since b is still not 0 and b is greater than a: b = 10 - 5 = 5
       
   172 Step 5: Since b is still not 0 and b is equal to a: b = 5 - 5 = 0
       
   173 Step 6: Since b is 0 the gcd is a = 5 which is returned
       
   174 
       
   175 If we adopt the modulo operation instead of subtraction and follow the
       
   176 steps:
       
   177 
       
   178 Step 1: a = 25 b = 5: Since both a and b are not 0 and b is greater
       
   179 than a: b = 25 % 5 = 0
       
   180 Step 2: Since b is 0 the gcd is a = 5 which is returned
       
   181 
       
   182 Wow! That was overwhelmingly lesser number of steps! So now we are
       
   183 convinced that if we replace the subtraction operation with the modulo
       
   184 operation our code performs much better. But if we think carefully we
       
   185 know that the modulo of a and b is less than b irrespective of how
       
   186 large the value of a is, including the case where a is already less
       
   187 than b. So we can eliminate that extra conditional **if** statement by
       
   188 just swapping the result of the modulo operation to the position of b
       
   189 and b to the position of a. This ensures that a is always greater than
       
   190 b and if not the swapping combined with modulo operation takes care of
       
   191 it. To exemplify it, if a = 5 and b = 25 then by swapping and
       
   192 performing modulo we have a = b = 25 and b = a % b = 5 % 25 = 5 and
       
   193 hence we proceed. So let us replace our original code with this new
       
   194 improved code we have come up with simple observations::
       
   195 
       
   196   def gcd(a, b):
       
   197       while b != 0:
       
   198           a, b = b, a % b
       
   199       return a
       
   200 
       
   201 Executing our script again we will see that all the tests pass. One
       
   202 final improvement we can think of which is not necessary in terms of
       
   203 efficiency but is certainly good to do keeping in mind the readability
       
   204 is that we can use the concept of recursion for the same
       
   205 algorithm. Without going into much detail this is how the code looks
       
   206 if we use a recursive approach::
       
   207 
       
   208   def gcd(a, b):
       
   209       if b == 0:
       
   210           return a
       
   211       return gcd(b, a%b)
       
   212 
       
   213 Much shorter and sweeter! And it passes all the tests!
       
   214 
    79 
   215 
    80 More realistic "Tests"
   216 More realistic "Tests"
    81 ======================
   217 ======================
    82 
   218 
    83 Now we have completed writing our first test. Let us start writing tests
   219 Now we have completed writing our first test. Let us start writing tests