[Tutor] unit testing

Roeland Rengelink r.b.rigilink@chello.nl
Tue, 18 Sep 2001 09:59:32 +0200


Timothy Wilson wrote:
> 
> Hi everyone,
> 
> I'd like to introduce the concept of unit testing to my beginning progamming
> students as early in the year as possible. I think they'll be motivated to
> learn it because our first project was to write a little leap year
> calculator. They spent a lot of time entering the same years over and over
> again while testing their program logic.
> 

I think it's a great idea to introduce unit testing early on.

One route may be to introduce the assert statement specifically for
testing purposes.
Something like:

def test_leap()
    assert is_leap(1991)==0
    assert is_leap(1992)==1

Something to impress early on is that testing new functionality in your
code should be done by writing new test functions, e.g.:

def test_century_leap():
    assert is_leap(1900)==0
    assert is_leap(2000)==1

I used to modify existing test functions to test new functionality, and
never reaped the real benefits of unit testing, which is the assurance
that after modifying or extending your code, it still works. 

Of course you end up with a lot of test functions this way, so some way
to automate execution of all the tests would be usefull. See below for a
simplified testing framework

It may be difficult to convince your student that writing all these
tests is benificial. You can easily end up with more code in your tests
than in your actual program. One way to convince them may be to tell
them to write tests before writing the program. It is an interesting
exercise to translate specifications into test code. And although the
tests may be longer than the program, they may be shorter than the
specification document. For example, let them think about what should
happen here:

def test_bad_imput():
    assert is_leap(0) == ???
    assert is_leap('abc') == ???

or here?

def test_questionable_input():
    assert is_leap(1993.7) == ???
    assert is_leap('2000') == ???
    assert is_leap('MCMXXXIII') == ???

Although if you want these calls to raise exceptions, you may have to
think a little about how to assert that something raises an error. This
may be too advanced a subject for beginning students. (For a solution
see end of this post)

> I don't think they're quite ready for PyUnit. Does anyone have any useful
> resources or hints about doing basic unit testing?
> 

PyUnit carries a lot of extra baggage that you don't need. Below you'll
find a bare-bones testing framework that does essentially the same thing
(and may be a good intro to PyUnit). 

class TestSuite:
    '''Create a suite of test functions, which can be tested together

    suite = TestSuite(func1, func2, ..., funcN)
    suite.run()

    calls each function func1,...,funcN in turn

    The functions should take no argument and return values are ignored.
    If a function raises an AssertionError it is assumed to have failed.
    If a funtion raises any other exception it is an error.
    If no exceptions are raised it is assumed to be a succes.

    Results are reported to the standard output.

    If test failures or errors are reported run these function 
    separately to get the traceback and the sepcific error messages
    '''

    def __init__(self, *test_functions):
        self.test_functions = test_functions

    def run(self):        
        for test in self.test_functions:
            print test.__name__+'...',
            try:
                test()
            except AssertionError:
                print 'FAILED'
            except:
                print 'ERROR'
            else:
                print 'OK'

# Example usage:

def test_succes():
    assert 1==1

def test_failure():
    assert 1==0

def test_error():
    assert 1/0 == None

suite = TestSuite(test_succes, test_failure, test_error)

if __name__=='__main__':
    suite.run()

--
A function to test if a function call raises an exception:

def raises(exception, function, *arguments):
    try:
        function(*arguments)
        return 0
    except exception:
        return 1

Use for example with:

def raise_ValueError():
    raise ValueError

def test_raises():
    assert raises(ValueError, raise_ValueError)



Hope this helps,

Roeland
--
r.b.rigilink@chello.nl

"Half of what I say is nonsense. Unfortunately I don't know which half"