nose is my Python testing tool of choice. You can use it to very easily create test suites.

While it does support the unittest module which is included with Python, it also allows for a much simple style using just methods and asserts.

Put the following code into a file called test_basic.py:

<notextile>def test_addition():
    print 2 + 2
    assert 2 + 2 == 4
def test_addition_wrong():
    print 2 + 3
    assert 2 + 3 == 6</notextile>

Then just run nosetests in the directory where you stored this file. You’ll
get an output indicating, that there is a test failure.

nose is based completely on naming conventions. Make sure your test files and methods all start with test for them to get executed. For assertions, just use assert.

When a test goes wrong you usually need some additional output to know what happened. nose helps you with this by handling print statements nicely. In the example above, as test_addition_wrong fails, you’ll see the output of the print statement print 2 + 3. But the print output of test_addition is suppressed because that test does not fail.

One of my favourite features of nose is Test generators. Often you have a large list of inputs and corresponding expected outputs for a function. Test generators allow you to easily test all of them without having to repeat the test code many times.

An example from a Nektoon test suite:

<notextile>def test_browsers():
    tests = [('Mozilla/4.0 (compatible; MSIE 8.0)', 'msie', '8'),
        ('Mozilla/4.0 (compatible; MSIE 7.0b)', 'msie', '7')]
    for ua, model, version in tests:
        yield check_browser, ua, model, version
def check_browser(ua, model, version):
    res = parse_ua(ua)
    print res
    assert res['model'] == model
    assert res['version'] == version</notextile>

The tests list is of course much bigger in our test suite and that’s the beauty of it. Adding a new test is very easy, but nose still provides very meaningful error output in the case of a test failure.

Read the official nose documentation if I was able to wet your appetite.

This post is part of the Python on the toilet series.