Tuesday, June 8, 2010

My little unit test trick

This is an oldie but a goodie.

I love writing unit tests for Python code. It makes me so happy seeing the little dots go by. Add in some coverage.py and you can even make a game out of how much your code is covered. Of course, adding in Hudson just makes it even better.

However, sometimes when your unit tests get sophisticated it can be a pain to introspect via the Python shell (REPL) on one terminal shell and then go back to the unittest. Especially when the unit tests get even the least bit sophisticated. In the shell you can forget steps since you are entering things manually.

So as soon as things get the least bit complicated I simply start using the Python help() function and pdb library inside my test code. For example:

class MyTests(unittest):
    def test_pretending_to_be_complex(self):
        ...
        complex_object = really_complex_actions()
        ...
        
        # help demonstration
        help(complex_object)

        # PDB example cause everyone loves that too.
        import pdb        
        pdb.set_trace()

So what does this give you? Well, the help() function acts here exactly the same way it does from the Python shell. It stops the code processing and lets you do introspection. pdb lets you step through things with joy.

Try it out!

EDIT: Of course, you probably wouldn't use both help and pdb. Thats because you can call help() inside of PDB. My example just shows you available options. Thanks to Gary for pointing this out!

8 comments:

Eric Florenzano said...

I've read through this a few times, and I'm embarrassed to admit that I don't understand what you're getting at here.

EOL (Eric O LEBIGOT) said...

Thank you so much for sharing! Interesting and useful.

Brandon Craig Rhodes said...

I only wish the debugger could be invoked with a builtin so that we could stop having to add an inline "import" statement everywhere we needed PDB! Anyway, nice point about how the debugger can be so easily invoked even deep inside of a series of tests.

Anonymous said...

It seems like a very good practice. Thank you!

I'm going to give it a try in the near future.

pydanny said...

Eric Florenzano: Try it with a simple example. Create a simple unittest and stick in help(str). The test will be interrupted by the help() interface.

Gary Robinson said...

To tell the truth, I don't see the utility of calling help() before calling pdb.set_trace() in your example. I insert set_trace() calls into my test scripts all the time when I'm debugging.

I use it not only for stepping but also for examining variables. If I want to see the docstring for a particular object when I'm in pdb, I can do !help(object).

You say calling help() "stops the code processing and lets you do introspection." But just calling set_trace(), without a preceding help(), also stops the code processing and lets you do whatever you want in the pdb environment.

Please let me know if there's something I'm missing.

pydanny said...

Gary: I guess I should be more explicit. You can use the help and pdb functions. I tend to use one or the either, not both. I'll edit the post to reflect this thought.

akaihola said...

For fans of nose, django-nose and IPython, here's a tweaked fork of the ipdb package:

http://github.com/akaihola/ipdb

Drop this in your code:

import ipdb;ipdb.set_trace()

And you'll get the fancy IPython-enchanced debugger even in the middle of a nose test run.