Running doctests from TextMate for Google App Engine modules

Developer emptor: I just lost a couple of hours to this: make sure you disable the Google App Engine doctest import in your apps when you're done testing a module lest you encounter _weird_ errors. I started having the login URL returned by users.create_login_url() being returned incorrectly when I forgot to remove the doctest import. It started forwarding to https://www.google.com/accounts/Login?continue=. Check out my forum post on it here.

I love Python's doctests. Basically, you test out your functions in the interactive shell and copy the results into the comments for a function. That's it! So simple.

Example:

def http_request(self, url, data, method=urlfetch.POST):
  """
  Makes an API call to Triggermail and returns the response.

  >>> client = TriggerMail()
  >>> client.http_request('email', {'email':'EMAIL_REMOVED'}, urlfetch.GET)
  {u'blacklist': u'0', u'templates': {u'test2': 0}, u'verified': u'0', u'vars': {u'first_name': u'Aral', u'last_name': u'Balkan'}, u'optout': u'0'}
	"""

To run the doctests, you just need a main method in your module that looks like this:

if __name__ == "__main__":
  import doctest
	doctest.testmod()

And, if you're working with TextMate, you can run the current script and its doctests by pressing ⌘ R.

Sweet!

However, when working with Google App Engine, this doesn't work out of the box.

If you try it, you'll get an error similar to the following:

ImportError: No module named google.appengine.api

This is because the local GAE environment isn't set up properly. The same goes when trying to test your apps from the Python interactive shell.

(If you're using Django for your app, you're in luck, all you have to do is ./manage.py shell and you're up and running with an interactive shell that's configured for your GAE project.)

Thankfully, Duncan over at the GAE forums went to the trouble of finding out exactly which imports are necessary to get you up and running.

His code listing actually goes beyond setting up the environment to finding your modules and running the tests. For my purposes, I just want to be able to hit ⌘ R in TextMate and run the tests for my current module while developing it, so I took the top bit of his code and put it into a module called gae_doctests.py.

It looks like this:

# To enable doctests to run from TextMate, import this module
# (Use only when testing, then comment out.)
# From: http://groups.google.com/group/google-appengine/browse_thread/thread/fa81f6abd95aa8b9/efed988b302aafb4?lnk=gst&q=duncan+doctests#efed988b302aafb4

import sys
import os
sys.path = sys.path + ['/usr/local/google_appengine', '/usr/local/google_appengine/lib/django', '/usr/local/google_appengine/lib/webob', '/usr/local/google_appengine/lib/yaml/lib', '/usr/local/google_appengine/google/appengine','/Users/aral/singularity/']

from google.appengine.api import apiproxy_stub_map
from google.appengine.api import datastore_file_stub
from google.appengine.api import mail_stub
from google.appengine.api import urlfetch_stub
from google.appengine.api import user_service_stub

APP_ID = u'test_app'
AUTH_DOMAIN = 'gmail.com'
LOGGED_IN_USER = 't...@example.com'  # set to '' for no logged in user

# Start with a fresh api proxy.
apiproxy_stub_map.apiproxy = apiproxy_stub_map.APIProxyStubMap()

# Use a fresh stub datastore.
stub = datastore_file_stub.DatastoreFileStub(APP_ID, '/dev/null', '/dev/null')
apiproxy_stub_map.apiproxy.RegisterStub('datastore_v3', stub)

# Use a fresh stub UserService.
apiproxy_stub_map.apiproxy.RegisterStub('user',
user_service_stub.UserServiceStub())
os.environ['AUTH_DOMAIN'] = AUTH_DOMAIN
os.environ['USER_EMAIL'] = LOGGED_IN_USER

# Use a fresh urlfetch stub.
apiproxy_stub_map.apiproxy.RegisterStub(
    'urlfetch', urlfetch_stub.URLFetchServiceStub())

# Use a fresh mail stub.
apiproxy_stub_map.apiproxy.RegisterStub(
  'mail', mail_stub.MailServiceStub())

(Either replace the /usr/local/ bit with the actual path to your GAE install or use Duncans code which is neater -- I was lazy and copied the contents of the sys.path list from the Django interactive shell.)

To use it, simply:

import gae_doctests

And hit ⌘ R in TextMate. Sweet!

Once I'm done hacking away on a module, I simply comment out the import.

Comments