1. Setup
Download gaeunit and extract it in your application directory and create a folder 'test'. Your application directory should look something like this:
$ ls helloper/
app.yaml gaeunit.py main.py test
We want gaeunit to run our tests when we browse to /test, so edit app.yaml to pass all URLs beginning with /test to gaeunit.py:
application: helloper
version: 1
runtime: python
api_version: 1
handlers:
- url: /test.*
script: gaeunit.py
- url: .*
script: main.py
Browsing to /test now should show there's no tests to run.
2. Write Your First Test (test/test_app.py)
Imagine we want to create a site where users drop messages to us. We start designing it by creating a test that define the URL schema, request handlers and database models.
import unittest
from StringIO import StringIO
from google.appengine.ext.webapp import Request
from google.appengine.ext.webapp import Response
from main import MainHandler
from main import Message
class Test(unittest.TestCase):
def test_post_message(self):
form = 'msg=hello'
handler = MainHandler()
handler.request = Request({
'REQUEST_METHOD': 'POST', 'PATH_INFO': '/',
'wsgi.input': StringIO(form),
'CONTENT_LENGTH': len(form),
'SERVER_NAME': 'hi',
'SERVER_PORT': '80',
'wsgi.url_scheme': 'http',
})
handler.response = Response()
handler.post()
messages = [m for m in Message.all()]
self.failUnless(len(messages) == 1)
self.failUnless(messages[0].msg == 'hello')
TIP: 'msg=hello' above works fine in this case, but to pass multiple POST parameters you need to create a URL encoded string:
>>> form = {
... 'first_name': 'Per',
... 'last_name': 'Thulin',
... }
>>> from urllib import urlencode
>>> urlencode(form)
'first_name=Per&last_name=Thulin'
When we run the test at this point, it will fail as we haven't written the code yet.
3. Write Code (main.py)
from google.appengine.ext import webapp
from google.appengine.ext.webapp import util
from google.appengine.ext import db
TEMPLATE = """
<html>
<body>
<p>%s</p>
<form action="/" method="post">
<input type="text" name="msg" />
<input type="submit" value="Drop message" />
</form>
</body>
</html>
"""
class Message(db.Model):
date = db.DateTimeProperty(auto_now_add=True)
msg = db.StringProperty()
class MainHandler(webapp.RequestHandler):
def get(self):
messages = [m for m in Message.all().order('-date')]
self.response.out.write(TEMPLATE % messages[0].msg)
def post(self):
m = Message(msg=self.request.get('msg'))
m.put()
self.redirect('/')
def main():
application = webapp.WSGIApplication([('/', MainHandler)],
debug=True)
util.run_wsgi_app(application)
Keep in mind
- It's far more fun to write tests as part of the design process rather than afterwords for verification.
- Keep the tests high level so that under the hood changes won't break the tests.
- Avoid testing HTML/presentation. These tests tend to become a burden and frequently break. E.g. verify that a given response is text/html but don't dig down the markup.
Exercise
Add a new RequestHandler that sends back a list of messages in JSON when a client make a GET request to the /messages URL. Begin by creating the test case. Tip, look at the Response class API for ways to test the response.
Screencast
WebTest would probably make all that request and response creation a lot simpler; you just do app=webtest.TestApp(webapp.WSGIApplication(...)); app.post('/', {'msg': 'hello'})
SvaraRadera+1 for WebTest :)
SvaraRaderaWe use nose-gae. It has it's pros and cons, but mostly we're very happy with it.
SvaraRaderaI keep getting;
SvaraRaderaTraceback (most recent call last):
File "C:\Languages\Python\Base\Python25\lib\unittest.py", line 260, in run
testMethod()
File "test_uploads.py", line 13, in test_post_message
'wsgi.input': StringIO(form),
TypeError: 'module' object is not callable
Any ideas?