15-112: Fundamentals of Programming and Computer Science
Class Notes: Testing and Debugging



  1. Testing
    # Sample code for our discussion on writing good test functions.
    # Your test functions should include as many tests as necessary,
    # but not more.  Each test should have a reason to be there,
    # covering some interesting edge case or scenario.
    
    def testIsPrime():
        # print "Testing isPrime()...",
        assert(isPrime(-1) == False) # negative
        assert(isPrime(0) == False)  # zero
        assert(isPrime(1) == False)  # 1 is quite the special case
        assert(isPrime(2) == True)   # 2, only even prime
        assert(isPrime(3) == True)   # 3, smallest odd prime
        assert(isPrime(4) == False)  # 4, smallest even non-prime
        assert(isPrime(9) == False)  # 9, perfect square of odd prime
        assert(isPrime(997) == True) # somewhat larger prime
        # print "Passed!"
    
    def workingIsPrime1(n):
        if (n < 2): return False
        for factor in xrange(2,n):
            if (n % factor == 0):
                return False
        return True
    
    def workingIsPrime2(n):
        if (n == 2): return True
        if ((n < 2) or (n % 2 == 0)): return False
        for factor in xrange(2,int(round(n**0.5))+1):
            if (n % factor == 0): return False
        return True
    
    def brokenIsPrime1(n):
        # if (n < 2): return False # broken (commented out)
        for factor in xrange(2,n):
            if (n % factor == 0):
                return False
        return True
    
    def brokenIsPrime2(n):
        if (n < 1): return False # broken: 2 -> 1
        for factor in xrange(2,n):
            if (n % factor == 0):
                return False
        return True
    
    def brokenIsPrime3(n):
        if (n < 2): return False
        for factor in xrange(2,n+1): # broken: n -> n+1
            if (n % factor == 0):
                return False
        return True
    
    def brokenIsPrime4(n):
        if (n < 2): return False
        for factor in xrange(2,n):
            if (n % factor == 0):
                return False
            else:                   # broken: no "else", should be after loop
                return True
    
    def brokenIsPrime5(n):
        if (n == 2): return True
        if ((n < 2) or (n % 2 == 0)): return False
        # for factor in xrange(2,int(round(n**0.5))+1):
        for factor in xrange(2,int(round(n**0.5))): # broken, omitted +1
            if (n % factor == 0): return False
        return True
    
    def raisesAssertion(f, *args):
        # Helper fn for testing test function.  You are responsible
        # for what this function does, but not how it does it.
        try: f(*args)
        except AssertionError: return True
        return False
    
    def testTestIsPrime():
        print "Testing testIsPrime()...",
        global isPrime
        # Store the "real" function so we can restore it after our tests
        try: realIsPrime = isPrime
        except: realIsPrime = None
        # Now test our working and broken versions
        isPrime = workingIsPrime1
        assert(raisesAssertion(testIsPrime) == False)
        isPrime = workingIsPrime2
        assert(raisesAssertion(testIsPrime) == False)
        isPrime = brokenIsPrime1
        assert(raisesAssertion(testIsPrime) == True)
        isPrime = brokenIsPrime2
        assert(raisesAssertion(testIsPrime) == True)
        isPrime = brokenIsPrime3
        assert(raisesAssertion(testIsPrime) == True)
        isPrime = brokenIsPrime4
        assert(raisesAssertion(testIsPrime) == True)
        isPrime = brokenIsPrime5
        assert(raisesAssertion(testIsPrime) == True)
        # And restore the "real" version
        isPrime = realIsPrime
        print "Passed!"
    
    testTestIsPrime()
    

  2. Debugging
    # Sample code for our discussion on basic debugging techniques.
    # The first example is correct (and probably should be ignored),
    # and the next two have bugs in them.
    #
    # The keys to basic debugging are:
    #   1) Find the simplest failure case possible
    #      (It is easier to trace smaller values...)
    #   2) Add print statements copiously in your code
    #      (printing the values of relevant variables)
    #   3) Carefully verify each line of the output,
    #      until you locate the error.
    
    def longDivision(num, den):
        # return num / den, by long division
        # only for non-negative num and positive den
        assert((num >= 0) and (den > 0))
        result = 0
        remainder = 0
        for k in xrange(digitCount(num)-1, -1, -1):
            remainder = 10*remainder + kthDigit(num, k)
            result += remainder/den * 10**k
            remainder %= den
        return (result, remainder)
    
    def longDivision1(num, den):
        # return num / den, by long division
        # only for non-negative num and positive den
        assert((num >= 0) and (den > 0))
        result = 0
        remainder = 0
        for k in xrange(digitCount(num), 0, -1):
            print "k=", k
            remainder = 10*remainder + kthDigit(num, k)
            result += remainder/den * 10**k
            remainder %= den
            print "remainder=", remainder
        return (result, remainder)
    
    print longDivision1(50, 5) # (10, 0), works!
    print longDivision1(1234, 72) # (10, 51) but should be (17, 10), fails!
    
    def longDivision2(num, den):
        # return num / den, by long division
        # only for non-negative num and positive den
        assert((num >= 0) and (den > 0))
        result = 0
        remainder = 0
        for k in xrange(digitCount(den)-1, -1, -1):
            remainder = 10*remainder + kthDigit(num, k)
            result += remainder/den * 10**k
            remainder %= den
        return (result, remainder)
    
    print longDivision2(50, 10) # (5, 0), works!
    print longDivision2(1234, 72) # (0, 34) but should be (17, 10), fails!
    
    def testLongDivision():
        print "Testing longDivision()...",
        for x in xrange(0,100):
            for y in xrange(1, 100):
                assert(longDivision(x, y) == (x/y, x%y))
        print "passed!"
    
    testLongDivision()