TDD With Python and Pylons
I’ve been doing some development on a website using Python and the Pylons web framework. I’m trying to stay pretty strict with Test Driven Development (TDD) though I run into problems because I’m still a complete novice with Pylons and a half-complete novice with Python. In my experience so far, unit tests are moderately difficult in Pylons and turn out to be something closer to the bastard stepchild of a redneck unit test and a 5th Avenue socialite mom. I feel that way because the unit tests require the Pylons framework to be set up correctly. They also often go outside their boundaries and I haven’t looked into any mock frameworks, though I have the feeling that using a mock framework with a dynamic language like Python is probably a stupid thing to say in public.
Regardless, I have really started to enjoy working with Pylons and that stems from the actual functional tests that are available through the framework. Specifically, my development flow has been something like this:
- Write new functional test of the web site
- Run tests to see them error out. Typical error message is that an action isn’t implemented on the controller.
- Implement the basic controller and the action but leave out the functionality under test.
- Rerun the test to see it fail.
- Implement the functionality necessary to get a passing test. This often includes implementing database tables, keys, getting SqlAlchemy set up to correctly map data to objects and creating new templates for HTML.
The functional tests are nice because while they aren’t confirming look and feel type stuff, they at least put the flow of the application under test. I’m a purist when it comes to having unit tests only test the code they are intended for but I’m not a purist when it comes to writing unit tests first as the only way to design the application. With a website like this, I’m pretty happy writing functional tests to drive out the design of the web site.
Here are some code snippets from my current website (try to ignore the fact that this looks like it might have something to do with horses and the Kentucky Derby, especially if you work for the NSA.)
First a test:
from darlydowns.tests import * from darlydowns.model import meta from darlydowns.model import horse class TestHorseController(TestController): def test_index(self): # setting up a temp horse to make sure one exists for the test tempHorse = horse.Horse('my temp', 'my description') meta.Session.save(tempHorse) meta.Session.commit() response = self.app.get(url_for(controller='horse')) ## Test response... assert len(response.c.horses) > 0 meta.Session.delete(tempHorse) meta.Session.commit()
Here we have a test that tests the response from a request for a URL that is handled by the controller “horse”. This controller grabs all the horses in the database and displays them in a table. The test saves a temp horse, gets the list, verifies the list contains at least 1 horse and then deletes the temp horse to clean up after itself.
Here’s the controller code that allows the test to pass:
import logging from darlydowns.model import meta from darlydowns.lib.base import * from darlydowns.model import horse from darlydowns.model.horse import Horse log = logging.getLogger(__name__) class HorseController(BaseController): def index(self): c.horses = [horse for horse in meta.Session.query(Horse).all()] return render('/horses.mako')
One of the beauties of Pylons is how little code is required to do something, once the project is set up. Here our HorseController has an action of “index” which is defined as a method. It grabs all the horses from the database and then uses a list comprehension to collect them into the c.horses variable. It then renders the template “horses.mako” which knows how to layout the web page using the horses found in the database.
Once this was done, I wrote code to save a horse, all driven out by the functional tests. I’ve been pretty happy with how the design is driven from these tests as it’s often quite clear where to proceed next in the application from the last test. Lots of times, other functionality comes up that doesn’t logically flow next but I just add that to a growing todo list in the project to make sure nothing is skipped.
Pylons takes a little getting used to, especially since I come from a static, everything in once solution sort of background. With Pylons, you need to learn Routes and Mako and SqlAlchemy but once you get your head around all those tools, it’s a joy to work with.
UPDATE: Welcome to everyone coming here from the Python subreddit. If you have any tips for testing using Pylons, feel free to drop me a comment. I’d love to hear about other people’s experiences.