Testing Your TurboGears Application

This document is a draft that has not yet had community review.

Unit and functional testing of applications is key to being able to maintain an application over time. There has been plenty written about the value of testing, so I won't repeat that here. I'm assuming that you've decided you want to do automated tests and this document will help get you started.

TurboGears 0.8 uses TestGears to make it so that you don't need to create test suites manually. It is expected that TurboGears 0.9 will switch to Nose, but there should be no changes to your tests required to make the switch. Using TestGears, you can write your tests either as unittest.TestCase objects, or as simple test functions.

If you want to dive right in and see some living examples of tests, you can check out the source for turbogears.tests.test_controllers. That module defines a controller and exercises several different features of TurboGears, much in the same way that you would test your own controllers.

As of this writing, TurboGears does not yet provide special utility functions for writing database tests, so those are not yet covered in this document. The current version of this document also does not talk about functional testing. You might want to check out Selenium for those tests.

Where do I put my tests?

I usually put them in a Python package called "tests" in my project. In TurboGears 0.8, this is not created for you. This will change in the future.

How do I run my tests?

Assuming your tests are in your package, running them is easy. From the top level directory of your project, run:

python setup.py testgears

That's it! TestGears will look through your project's directories to find whatever tests it can.

Testing Methods Directly

TurboGears provides two functions in turbogears.tests.util that help you test your controller methods: call and createRequest. This section talks about the call function.

turbogears.tests.util.call lets you call a method of your controller without going through normal CherryPy processing and without templates being applied to the output. This makes it easy to test the logic of your controller method without worrying about the impact of layout on it.

Using it is easy: util.call(yourmethod, arg1, arg2, kw1=val1, kw2=val2). The return value is whatever your method returns. So, if you return a dictionary, as is common in TurboGears, you can inspect that dictionary and check values in it.

Let's look at a full example. Let's assume your controllers.py looks like this:

That may look familiar, as it's just the quickstart controllers.py with a title added. Then, you'd create a file in your package.tests directory called something like test_controllers.py. That file would look like this:

You can run that with the "python setup.py testgears" command mentioned previously, and you'll see that you've got a test! You'll notice in the test runner's output that the docstring of the test function is listed rather than the name of the function. That's an easy way to get more readable test output.

Calling the method with parameters is easy, but there is an important note. util.call short-circuits the output processing that TurboGears does to get you the dictionary back, just as the method returned it. However, it does not change the input processing. The parameters you pass to the method will go through any validators you have defined. When calling your methods this way, you'll generally want to pass in string arguments, just as CherryPy does.

Testing Methods More Like A Web Request

If you want to test with the full effect of CherryPy filters and TurboGears' template processing, you can use the util.createRequest function. The full signature of createRequest can give you an idea of what it can do: createRequest(request, method="GET", protocol="HTTP/1.1", headers={}, rfile=None, clientAddress="127.0.0.1", remoteHost="localhost", scheme="http"). Unlike util.call, util.createRequest does not return anything. To see what was sent back to the client, you should inspect cherrypy.response.

For simulating an HTTP POST, util.createRequest is pretty low level. It expects you to set up the headers as they would be set up for an HTTP POST.

Basic usage of createRequest is easy. Consider the example controller provided in the previous section. We can write this test:

You'll note from this test that cherrypy.response.body is a list.

Using this method of testing allows you to see that the template is being applied as expected. This works for studying the HTML content produced, but it falls short of an actual functional test because you can't verify that JavaScript code is functioning properly with the data returned. To do that kind of test, you'll need a tool like Selenium.