Monday, 5 May 2008

Unit testing with Google App Engine

The App Engine isn't too friendly for testing: you can't use interactive mode, and there's some setup needed to get a test harness.

I've found that using doctest works quite nicely with the appengine, provided you get that initial setup out of the way. If your application interacts with the datastore, then you probably want to set up some objects before querying them and to my mind that sits better with the doctest model of a long chatty document describing a pseudo-interactive session than the xUnit style of separate tests each starting from a clean system doing something and making a single assertion about the resulting state. For a particular project you can update the test framework to perform common setup for the tests.

The structure I've come up with sets up the environment to run tests under the appEngine framework. It looks for tests in two places: *.py files in the project directory, and tests/*.tests. For the Python files doctest will run tests from all docstrings found in the file (and you must have at least one docstring in every Python source file otherwise you'll get an error, but you don't have to actually have any tests in it). Each docstring forms a separate test. For the other files they are just plain text (or you can use reST) and the entire file is run as one test (so the state is preserved until the end of the file).

Here's a sample doctest file:

DateTime testing

The appengine DateTimeProperty property type just looks like an
ordinary datetime object when you use it.

>>> from datetime import datetime, timedelta
>>> from google.appengine.ext import db
>>> class Test(db.Model):
... date = db.DateTimeProperty(auto_now_add=True)
... date2 = db.DateTimeProperty()

>>> obj = Test(date2=datetime(2008,1,1))

The date attribute is set automatically to the current time, but we
can use ellipsis to skip over the part that varies and check that it
is very nearly the current time:

>>> obj.date #doctest: +ELLIPSIS
datetime.datetime(...)
>>> obj.date - datetime.now() < timedelta(1)
True

For the other attribute we can test the extact value:

>>> obj.date2 == datetime(2008,1,1)
True
>>> obj.date2.strftime("%a, %d %b %Y %H:%M:%S +0000")
'Tue, 01 Jan 2008 00:00:00 +0000'

 


Download an example appengine project with this test and execute tests/runtests.py to run it.

4 comments:

rbp said...

Very nice, thanks! I've had to os.path.expanduser GOOGLE_PATH, and I chose to doctest everything in tests/*, but otherwise this has been tremendous help :)

Hernán said...

How nice! That's exactly what I was looking for. Thanks a lot.

Søren Bjerregaard Vrist said...

Could you show us a more "real" test. Where you actually test some of the code that is going to be used

night-fairy-tales said...

good solution. Thanks