作者:HelloGitHub-追梦人物
文中所涉及的示例代码,已同步更新到 HelloGitHub-Team 仓库
先来建立测试文件的目录结构。首先在 comments 应用的目录下建立一个名为 tests 的 Python 包,然后删除 comments 应用下 django 自动生成的 tests.py 文件,防止和 tests 包冲突,再根据需要测试的内容,创建相应的 Python 模块。最终 tests 目录结构如下:
comments\ templatetags\ models.py ... tests\ __init__.py base.py test_models.py test_templatetags.py test_views.py其中 base.py 用于存放各个测试用例的公共的数据初始化基类。
数据基类由于评论必须和文章关联,因此我们首先来写一个数据基类,用于初始化生成文章数据,其它测试类继承这个数据基类,从而不用在每个测试类里都写一遍创建文章数据的代码了。
数据基类写在 base.py 模块里:
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 ModelComment Model 的代码逻辑比较简单,测试起来也很简单:
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(, email='a@a.com', text='评论内容', post=self.post, ) def test_str_representation(self): self.assertEqual(self.comment.__str__(), '评论者: 评论内容') 测试视图函数我们只有一个发表评论的视图函数,根据视图函数的逻辑,需要测试以下几点:
只处理 POST 请求,其它请求将返回 405 Method Not Allowed 错误码。
如果评论的文章不存在,返回 404 错误码。
如果提交的评论内容有错误(例如 email 格式不正确),将渲染 preview.html 预览页面,并且预览页面显示评论出错的消息提醒和评论表单中包含的错误。
提交的内容合法,则创建评论,用户被重定向回被评论文章的详情页,页面中包含评论成功的消息提醒。
具体代码如下(省略掉了一些简单的一看就懂的测试用例):
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': ['这个字段是必填项。']}