I'm interested in learning how to Doctests and Unit tests in a more Agile / BDD way.
I've found a few tutorials that seem reasonable, but they are just thumbnails.
What I would really like to see is the source code of some Django projects that were developed BDD style.
The things I'm unclear about are how do you handle request objects etc.
I have a situation where I have deployed my app and I'm getting completely different behavior in production that I did in development or even from the Python shell on the production server. I'm hoping some Doctests will help me diagnose this and well as open the door for a more Agile process of writing the tests first.
Specifically, here is the code I'm trying to test:
def match_pictures_with_products( queryset, number_of_images = 3):
products = []
i = 0
for product in queryset:
if i < ( number_of_images ):
image = product.imagemain_set.all()[:1]
product.photo_url = image[0].photo.url
products.append(product)
i += 1
return products
def index(request):
"""returns the top 10 most clicked products"""
products = Product.objects.all()[:10]
products = match_pictures_with_products( products, 10) .
return render_to_response('products/product_list.html', {'products': products})
How do I create a Doctest that ensures that index returns 10 objects?
The Product queries seem to work fine from the shell on the production server. The actual server is not returning any products at all.
I've asked myself the same question before. I've found doctests to be of limited utility for things like views, model methods and managers because
- You need to be able to setup and teardown a test data set to actually use for testing
- Views need to take a request object. In a doctest, where does that come from?
For that reason, I've always used the Django unit testing framework which handles all this for you. Unfortunately, though, you don't get some of the benefits of the doctests and it makes TDD/BDD harder to do. What follows next is pure speculation about how you might make this work:
I think you'd want to grab doctests from their respective modules and functions and execute them within the unit testing framework. This would take care of test data setup/teardown. If your doctests were executed from within a test method of something that subclasses Django's unittest.TestCase they'd be able to use that test DB. You'd also be able to pass a mock request object into the doc test's execution context. Here's a Django snippet that provides a mock request object and info on it. Let's say you wanted to test the docstrings from all of an applications views. You could do something like this in tests.py :
from ??? import RequestFactory
from doctest import testmod, DocTestFailure
from django.test import TestCase
from myapp import views
class MyAppTest(TestCase):
fixtures = ['test_data.json']
def test_doctests(self):
try:
testmod(views, extraglobs={
'REQUEST': RequestFactory()
}, raise_on_error=True)
except DocTestFailure, e:
self.fail(e)
This should allow you to do something like this:
def index(request):
"""
returns the top 10 most clicked products
>>> response = index(REQUEST)
>>> [test response content here]
"""
products = Product.objects.all()[:10]
products = match_pictures_with_products( products, 10) .
return render_to_response('products/product_list.html', {'products': products})
Again, this is just off the top of my head and not at all tested, but it's the only way that I think you could what you want without just putting all your view tests in the unit testing framework.
The way your view is written, it would be hard to test. You'd have to scrape the html to see if the content you want is present, and then you're testing more than you need to. Better would be to rewrite your view to make it easier to test. Start by parameterizing your template name, so you can create a simple test template:
def index(request, template_name='products/product_list.html'):
"""returns the top 10 most clicked products"""
products = Product.objects.all()[:10]
products = match_pictures_with_products( products, 10) .
return render_to_response(template_name, {'products': products})
Then you can write a simple template that just counts the number of products:
{{ products.count }}
And make sure the template returns "10".
You can use the django testclient and test the context variables that get set:
>>> response = client.get('/foo/')
>>> response.context['name']
'Arthur'
You can also check the response code to make sure the page returned a success 200
.
The zope.testbrowser package might be useful in your doctests, since you want to analyse the rendered HTML answer of your production server.