Chapter 14. Automatic testing with PyUnit

Table of Contents
About unittests
Starting out
A first testcase
Collecting tests in a test suite
A more complicated test
Large projects
Testing signals and slots
Conclusion

In Chapter 12, we created an application framework in which the GUI interface was separate from the application logic. One of the reasons for this was to make it easier to test the components in isolation. Testing your software components separately is called unit-testing, and it has proven over the past few years to be a very good way of ensuring software quality. Python supports working with unit-tests out of the box: since version 2.1, a special module, unittest.py, is included in the standard distribution. In this chapter, we will write a unittest for the document module from the document-view framework.

About unittests

Have you ever done maintenance on a large application and broken something because you changed something, somewhere? Or worse, only noticed the breakage when a user mailed you? Have you ever begun writing an application, but were unable to complete it because the whole castle of cards collapsed due to excessive fragility?

It has probably happened to you, and it certainly has happened to me. Testing software is a boring chore, and besides, everything has to be finished yesterday, or today at the latest. However, not testing will cost you a lot of time, too, and it's more fun to program than to bug-hunt. It would be best if automated testing could be made part of the edit-compile-crash cycle.

This has occurred before to a lot of people, but the honor of ‘inventing' automatic unit-testing belongs to Erich Gamma and Kent Beck - familiar names to developers everywhere. They started writing unit-test frameworks for SmallTalk, moving on to Java and other languages.

The idea is simple but ingenuous: first the developer writes his test, then the class that will make the test work; the process is repeated until the created code fits the application you're developing. All the while, you will get instant feedback from a small GUI app that runs your tests and shows you a green progressbar when everything works as intended, and a horrible, unfriendly red progressbar when a test fails. You can also run the unittests without a gui, but it isn't as much fun.

All is well - the bar is green!

Back to the drawing board; the bar is red, tests have failed!

Writing tests takes a bit of getting used-to, and it is something more easily learned when working together with someone who has done it before. However, once you get around to it, it is definitely addictive.

Unit-testing using the unittest.py framework also departs from what people are used to doing when testing: namely, writing scripts that simulate user input, such as mouse-clicks. Those scripting solutions are quite often so fragile that they are worse than useless. It is far better to explicitly code tests for the back-end of your application, guaranteeing that the interaction between backend and GUI is correct, as opposed to trying to deduce bugs from apparent errors at the GUI front.

In sum, the advantage of unit-testing is: you know you can depend upon the behavior of your components, and whenever you change a component, you will be alerted to that change by failing tests. In short, you will be able to trust your software at a relatively low level.

There a few disadvantages, too. You might be lulled into a false sense of security: if you change your unit-tests along with the code, then you can no longer be sure that your components fit your system, for you have just changed their behavior. A unittest is a kind of contract about the behavior your code exposes to the outside world. Changing the contract one-sidedly is a guarantee for breaking relations.

It's also quite difficult to find a good division between unit-tests and functional tests. Functional testing is mostly done from a user perspective; unit-tests test the behavior of your classes, but functional tests test the behavior of the application. There is currently no way to automate functional testing.

Cynics have noted that the running of unittests has negated all the progress made in creating fast compilers, and even virtually compilation-less languages such as Python. Indeed, running a full testsuite can take a long time. Fortunately, Pyunit is very fast.

Lastly, watching the bar stay green is addictive in itself, and you might be tempted to run working tests over and over again...