做者:HelloGitHub-追梦人物html
文中所涉及的示例代码,已同步更新到 HelloGitHub-Team 仓库python
评论应用的测试和博客应用测试的套路是同样的。git
先来创建测试文件的目录结构。首先在 comments 应用的目录下创建一个名为 tests 的 Python 包,而后删除 comments 应用下 django 自动生成的 tests.py 文件,防止和 tests 包冲突,再根据须要测试的内容,建立相应的 Python 模块。最终 tests 目录结构以下:github
comments\
templatetags\
models.py
...
tests\
__init__.py
base.py
test_models.py
test_templatetags.py
test_views.py
复制代码
其中 base.py 用于存放各个测试用例的公共的数据初始化基类。django
因为评论必须和文章关联,所以咱们首先来写一个数据基类,用于初始化生成文章数据,其它测试类继承这个数据基类,从而不用在每一个测试类里都写一遍建立文章数据的代码了。编程
数据基类写在 base.py 模块里:bash
comments/tests/base.py
from django.apps import apps
from django.contrib.auth.models import User
from django.test import TestCase
from blog.models import Category, Post
class CommentDataTestCase(TestCase):
def setUp(self):
apps.get_app_config('haystack').signal_processor.teardown()
self.user = User.objects.create_superuser(
username='admin',
email='admin@hellogithub.com',
password='admin'
)
self.cate = Category.objects.create(name='测试')
self.post = Post.objects.create(
title='测试标题',
body='测试内容',
category=self.cate,
author=self.user,
)
复制代码
要注意建立文章数据时,使用 apps.get_app_config('haystack').signal_processor.teardown()
断开建立索引的信号。微信
Comment Model 的代码逻辑比较简单,测试起来也很简单:app
comments/tests/test_models.py
from .base import CommentDataTestCase
from ..models import Comment
class CommentModelTestCase(CommentDataTestCase):
def setUp(self):
super().setUp()
self.comment = Comment.objects.create(
name='评论者',
email='a@a.com',
text='评论内容',
post=self.post,
)
def test_str_representation(self):
self.assertEqual(self.comment.__str__(), '评论者: 评论内容')
复制代码
咱们只有一个发表评论的视图函数,根据视图函数的逻辑,须要测试如下几点:函数
具体代码以下(省略掉了一些简单的一看就懂的测试用例):
comments/tests/test_views.py
from django.urls import reverse
from .base import CommentDataTestCase
from ..models import Comment
class CommentViewTestCase(CommentDataTestCase):
def setUp(self):
super().setUp()
self.url = reverse('comments:comment', kwargs={'post_pk': self.post.pk})
# 省略掉了一看就懂的测试用例...
def test_invalid_comment_data(self):
invalid_data = {
'email': 'invalid_email',
}
response = self.client.post(self.url, invalid_data)
self.assertTemplateUsed(response, 'comments/preview.html')
self.assertIn('post', response.context)
self.assertIn('form', response.context)
form = response.context['form']
for field_name, errors in form.errors.items():
for err in errors:
self.assertContains(response, err)
self.assertContains(response, '评论发表失败!请修改表单中的错误后从新提交。')
def test_valid_comment_data(self):
valid_data = {
'name': '评论者',
'email': 'a@a.com',
'text': '评论内容',
}
response = self.client.post(self.url, valid_data, follow=True)
self.assertRedirects(response, self.post.get_absolute_url())
self.assertContains(response, '评论发表成功!')
self.assertEqual(Comment.objects.count(), 1)
comment = Comment.objects.first()
self.assertEqual(comment.name, valid_data['name'])
self.assertEqual(comment.text, valid_data['text'])
复制代码
首先看到 test_invalid_comment_data
测试用例。这个测试用例中,咱们构造了一个缺失评论内容、评论人名字且邮箱格式不正确的数据,而后将其提交了评论。接着就是对预期结果的断言。这里关键的一点是,渲染的预览页面应该包含提示用户的表单错误。因此咱们从响应的上下文变量中取得表单 form 这个模板变量。接着使用以下代码获取表单的错误并断言响应中是否包含了这些错误:
for field_name, errors in form.errors.items():
for err in errors:
self.assertContains(response, err)
复制代码
一旦表单绑定了数据,而且 is_valid
方法被调用,就会有一个 errors
属性(参考评论视图函数中表单的处理逻辑)。errors
属性是一个类字典对象,若是表单数据不包含错误,则为空;若是包含错误数据,则其键为包含错误数据的字段名称,值为该字段错误提示构成的列表(一个字段可能包含多个错误,因此是一个列表)。例如这里的 form.errors
,若是将其打印出来(使用 print(repr(form.errors))
,str
方法返回的内容是经渲染的 ul 列表),能够看到它的内容以下:
{'name': ['这个字段是必填项。'], 'email': ['输入一个有效的 Email 地址。'], 'text': ['这个字段是必填项。']}
复制代码
test_valid_comment_data
中,咱们构造合法的评论内容并提交,预期结果是评论提交成功后重定向到被评论文章的详情页,因此使用了 assertRedirects
进行断言。
注意 self.client.post(self.url, valid_data, follow=True)
传入的 follow=True
参数。因为评论成功后须要重定向,所以传入 follow=True
,表示跟踪重定向,所以返回的响应,是最终重定向以后返回的响应(即被评论文章的详情页),若是传入 False,则不会追踪重定向,返回的响应就是一个响应码为 302 的重定向前响应。
对于重定向响应,使用 assertRedirects
进行断言,这个断言方法会对重定向的整个响应的过程进行检测,默认检测的是响应码从 302 变为 200。
上一篇中介绍过模板标签的测试方法。基本套路就是代替 django 视图函数自动渲染模板内容的过程,手工构造一个包含待测试模板标签的模板,而后手工渲染其内容,断言渲染后的内容是否包含预期的内容。具体代码请看源代码,这里再也不一一讲解,只将涉及的几个新的表单操做进行一个简单介绍。
class CommentExtraTestCase(CommentDataTestCase):
# ...省略其它测试用例的代码
def test_show_comment_form_with_invalid_bound_form(self):
template = Template(
'{% load comments_extras %}'
'{% show_comment_form post form %}'
)
invalid_data = {
'email': 'invalid_email',
}
form = CommentForm(data=invalid_data)
self.assertFalse(form.is_valid())
context = Context(show_comment_form(self.ctx, self.post, form=form))
expected_html = template.render(context)
for field in form:
label = '<label for="{}">{}:</label>'.format(field.id_for_label, field.label)
self.assertInHTML(label, expected_html)
self.assertInHTML(str(field), expected_html)
self.assertInHTML(str(field.errors), expected_html)
复制代码
看到循环表单 form 的语句:
for field in form:
label = '<label for="{}">{}:</label>'.format(field.id_for_label, field.label)
复制代码
咱们这里使用了 field
的两个属性,id_for_label
和 id_for_label
,分别是 django 表单自动生成的表单字段 label 的 id 和 label 名。别的就没什么好说的了,就是不停地断言页面包含预期的 HTML 内容。
至此,咱们完成了对 blog 应用和 comment 应用这两个核心 app 的测试。如今,咱们想知道的是,到底咱们的测试效果怎么样呢?测试充分吗?测试全面吗?还有没有没有测到的地方呢?
单凭肉眼观察难以回答上面的问题,接下来咱们就借助一个工具,从代码覆盖率的角度来检测一下咱们的测试效果究竟如何。
『讲解开源项目系列』——让对开源项目感兴趣的人再也不畏惧、让开源项目的发起者再也不孤单。跟着咱们的文章,你会发现编程的乐趣、使用和发现参与开源项目如此简单。欢迎联系我(微信:xueweihan,备注:讲解)加入咱们,让更多人爱上开源、贡献开源~