Testing

In all exercises from now on (i.e., bookmark this page! - also reachable from the web resources page) which include writing some function myfunction, think about the following before writing any code:

  1. What should myfunction do?

  2. How do I know that myfunction works?

  3. Automatic testing.

In most cases, this strategy is applicable, wise, and encouraged!


Example: Solution to Deitel exercise 4.4c): Write a function that determines whether a number is a prime. Let's call it is_prime.

  1. is_prime should take an integer n>0 as input and return 1 if n is a prime and 0 otherwise.
  2. There are infinitely many primes so we can't check all of them. But it should return 1 for all this input: 2, 3, 5, 7, 19, 31, 137, 881, and it should return 0 for all this input: 9, 14, 77, 100, 169.
  3. Put all these input/output checks in test_function and call this function from an if __name__=="__main__": wrapper until it performs correctly.

Here is a first solution (which might be saved in a file called ex4_4.py - see this tip):



import math

def is_prime(c):
    for i in range(2, int(math.sqrt(c))):
        if c%i == 0:
            return 0
    return 1


def test_function():

    # list of input/output tuples:
    inout = [(2, 1), (5, 1), (7, 1), (19, 1), (31, 1), (137, 1), (881, 1),
             (9, 0), (14, 0), (77, 0), (100, 0), (169, 0)]
    
    for input, output in inout:
        if is_prime(input) != output:
            print "oops: input %d gave wrong output" %input

    

if __name__=="__main__":
    test_function()



Now we run the program and get this result:
oops: input 9 gave wrong output
oops: input 169 gave wrong output

Hmm.. what is wrong with the above code? Well, 9 and 169 are quadratic numbers, do we check for their squareroots 3 and 13? Looking closely at the code we remember that the range(a, b) function includes a but not b in the resulting list, and so we don't actually test in is_prime whether the squareroot of n is a divisor. Thus we modify this line to:

    for i in range(2, 1+int(math.sqrt(n))):


.. and what do you know, all is well. Now we can turn the test_function call into a comment and write a program that handles the user interaction instead - or we can import this file as a module in another program that handles the interaction.

Of course we keep 9 and 169 (and the other numbers) in the test. For some functions we might discover special cases. These should go in the test - and stay there in case we change the function's implementation and need to run the test again.