Wednesday, April 26, 2006

In-process Web app testing with twill, wsgi_intercept and doctest

At the SoCal Piggies meeting last night, Titus showed us the world's simplest WSGI application:
def simple_app(environ, start_response):
status = '200 OK'
response_headers = [('Content-type','text/plain')]
start_response(status, response_headers)
return ['Hello world!\n']
(see Titus's intro on WSGI for more details on what it takes for a Web server and a Web app to talk WSGI)

How do you test this application though? One possibility is to hook it up to a WSGI-compliant server such as Apache + mod_scgi, then connect to the server on a port number and use a Web app testing tool such as twill to make sure the application serves up the canonical 'Hello world!' text.

But there's a much easier way to do it, with twill's wsgi_intercept hook. Titus wrote a nice howto about it, so I won't go into the gory details, except to show you all the code you need to test it (code provided courtesy of twill's author):
twill.add_wsgi_intercept('localhost', 8001, lambda: simple_app)
twill.shell.main()
That's it! Running the code above hooks up twill's wsgi_intercept into your simple_app, then drops you into a twill shell where you can execute commands such as:
>>> go("http://localhost:8001/")
==> at http://localhost:8001/
>>> show()
Hello world!

What happened is that wsgi_intercept mocked the HTTP interface by inserting hooks into httplib. So twill acts just as if it were going to an http URL, when in fact it's communicating with the application within the same process. This opens up all kinds of interesting avenues for testing:
  • easy test setup for unit/functional tests of Web application code (no HTTP setup necessary)
  • easy integration with code coverage and profiling tools, since both twill and your Web application are running in the same process
  • easy integration with PDB, the Python debugger -- which was the subject of Titus's lightning talk at PyCon06, where he showed how an exception in your Web app code is caught by twill, which then drops you into a pdb session that lets you examine your Web app code
But it gets even better: you can write doctests for your Web application code and embed twill commands in them. It will all work because of the wsgi_intercept trick. Here's a working example (yes, I accepted Titus's challenge and I made it work, at least with Python 2.4):

def simple_app(environ, start_response):
"""
>>> import twill
>>> twill.add_wsgi_intercept('localhost', 8001, lambda: simple_app)
>>> from twill.commands import *
>>> go("http://localhost:8001")
==> at http://localhost:8001
\'http://localhost:8001\'
>>> show()
Hello world!
BLANKLINE
\'Hello world!\\n\'
"""

status = '200 OK'
response_headers = [('Content-type','text/plain')]
start_response(status, response_headers)
return ['Hello world!\n']

if __name__ == '__main__':
import doctest
doctest.testmod()
(note that BLANKLINE needs to be surrounded with angle brackets, but the Blogger editor removes them...)

If you run this through python, you get a passing test. Some tricks I had to use:
  • indicating that the expected output contains a blank line -- I had to use the BLANKLINE directive, only available in Python 2.4
  • escaping single quotes and \n in the expected output -- otherwise doctest was choking on them
But here you have it: documentation and tests in one place, for Web application code that is generally hard to unit test. Doesn't get more agile than this :-)

No comments: