在敏捷/BDD中使用Django Doctest的示例
我想学习如何以更灵活和行为驱动开发(BDD)的方式进行文档测试和单元测试。
我找到了一些看起来还不错的教程,但它们只是一些简单的介绍。
我真正想要的是一些用BDD风格开发的Django项目的源代码。
我不太明白的是,如何处理请求对象等等。
我遇到的情况是,我的应用已经上线,但在生产环境中的表现和我在开发环境中,甚至在生产服务器的Python命令行中看到的完全不同。
我希望一些文档测试能帮助我诊断这个问题,同时也能为先写测试的灵活开发流程打开大门。
具体来说,我想测试的代码是:
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})
我该如何创建一个文档测试,以确保index返回10个对象?
在生产服务器的命令行中,产品查询似乎工作得很好,但实际服务器根本没有返回任何产品。
4 个回答
你可以使用 django的测试客户端 来测试那些被设置的上下文变量:
>>> response = client.get('/foo/')
>>> response.context['name']
'Arthur'
你还可以检查响应代码,以确保页面返回的是成功的 200
。
你现在写的这个视图(view)不太好测试。你得去抓取HTML代码,看看你想要的内容是否存在,这样的话你测试的内容就太多了。更好的做法是重写你的视图,让它更容易测试。首先,可以把模板的名字变成一个参数,这样你就可以创建一个简单的测试模板:
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})
然后你可以写一个简单的模板,只需要计算产品的数量:
{{ products.count }}
确保这个模板返回“10”。
我之前也问过自己同样的问题。我发现 doctests 在像视图、模型方法和管理器这些地方的用处有限,因为:
- 你需要能够设置和清理测试数据集,才能真正进行测试。
- 视图需要接收一个请求对象。在 doctest 中,这个对象从哪里来呢?
因此,我一直使用 Django 的 单元测试框架,它可以帮你处理这些问题。不过,遗憾的是,这样你就无法享受到 doctests 的一些好处,而且会让测试驱动开发(TDD)和行为驱动开发(BDD)变得更难。接下来是一些纯粹的猜测,关于你可能如何让这件事情运作:
我觉得你可以从各自的模块和函数中提取 doctests,并在单元测试框架中执行它们。这样就可以处理测试数据的设置和清理。如果你的 doctests 是在 Django 的 unittest.TestCase 子类的测试方法中执行的,它们就能使用那个测试数据库。你还可以将一个模拟的请求对象传入到 doc test 的执行环境中。这是一个 Django 代码片段,提供了一个模拟请求对象,以及 相关信息。假设你想测试一个应用中所有视图的文档字符串,你可以在 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)
这应该允许你做到类似这样的事情:
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})
再说一次,这只是我随便想的,并没有经过测试,但我认为这是在不把所有视图测试放入单元测试框架中的情况下,能够实现你想要的唯一方法。