February 22, 2008

Test Driven CherryPy Applications (Part 1)

Context

Model-View-Controller (MVC) pattern eases the development and improves the quality of web applications. Test-Driven Development (TDD) contributes also to the quality of the application and ideally each piece of the application should be testable. While writing unit tests for the controller and model layers is plain Python unit testing, writing tests for the view layer is not as direct.

CherryPy allow us to write plain Python classes for the view layer, but testing these classes functions means comparing expected HTML documents to actual ones. Frameworks such as Twill imply to run the application in order to test its view layer. We are going to use two other strategies for unit testing view classes as any python classes: Using fake template rendering and using BeautifulSoup that eases the querying of HTML documents.

Finally, in the following we are going to stick to the Single Responsibility Principle and reduce the responsibility of the view layer to presentation: selecting the proper template to be used, an relying on the controller to perform the real work.

Faking Template Rendering

The first strategy for testing view classes is to use fake templates. This imply that every view class is written using the following structure: The class explicitly depends upon a function that is used for rendering HTML.

In addition to supporting testing through the fake template rendering, this approach allow you to easily substitute template engines. The following example present three possible implementation of such a rendering function. These are not using any optimization such as using pre-compiled template objects. (To do so a callable object would have to be defined.)


def render_string_template(template, values):
return string.Template(template).substitute(values)

def render_cheetah_string_template(template, values):
return Template(template, searchList=[values]).respond()

def render_cheetah_file_template(template, values):
return Template(file=template, searchList=[values]).respond()
Let's consider the following stories for a simple application.
  • The user receive a form if she does not provide her name.
  • The user receive a greeting if she has provided her name.

The following tests check that when the greeting() function is called, it uses the proper template with the proper parameter values. The test also checks that the function is exposed as it is required by CherryPy. (An exposed function is mapped to an URL and can be request from the web client.)


class FakeRender:
def __call__(self, tmpl, values=None):
self.tmpl = tmpl
self.values = values


class TestMyView(unittest.TestCase):

def setUp(self):
self.render = FakeRender()
self.view = MyView(render=self.render)

def test_greeting_is_exposed(self):
self.assertTrue(self.view.greeting.exposed)

def test_greeting_returns_a_form_if_no_name_is_provided(self):
self.view.greeting()
self.assertEquals('form_tmpl', self.render.tmpl)
self.assertEquals(None, self.render.values)

def test_greeting_returns_a_greeting_with_the_provided_name(self):
self.view.greeting('world')
self.assertEquals('greeting_tmpl', self.render.tmpl)
self.assertEquals(dict(name='world'), self.render.values)

The following implementation passes the test.


class MyView:

def __init__(self, render):
self.render = render

@cherrypy.expose
def greeting(self, name=None):
if not name:
self.render('form_tmpl')
else:
self.render('greeting_tmpl', dict(name=name))


Remarks

This first solution of decoupling the rendering from the view permits to check that the proper template is used and the expected values are provided to the template. This can be reach without lauching the application server keeping an easy and faster to run test suite. This does not check that the rendered HTML document contains this information which is more related to template testing. It permits to focus on the testing of validation functions and proper call to controllers. Finally, I can let an more-competent-than-I-am person to set up a set of templates in order to have a nice looking web interface.