在自动化测试环节,我们已经了解了测试用例的意义,以及如何使用Django提供的Django.test.TestCase创建测试用例,这节课我们来学习测试视图
在自动化测试这节课,我们测试的是模型的was_published_recently()
方法,通过创建一个模型实例,然后考察他的was_published_recently()
结果。
视图测试与模型测试不一样的地方是我们需要模拟用户访问,就好像用户通过浏览器(客户端)访问一样,来进行测试
因此我们首先需要创建一个模拟客户端
Django测试客户端
运行python manage.py shell
进入shell环境
配置测试环境
>>> from django.test.utils import setup_test_environment
>>> setup_test_environment()
创建客户端
>>> from django.test import Client
>>> client = Client()
模拟客户端访问
>>> response = client.get('/')
Not Found: /
由于 "/"这个url并未定义对应的视图,所以结果是Not Fount
>>> response = client.get("/polls/")
>>> print(response)
<HttpResponse status_code=200, "text/html; charset=utf-8">
response状态码为200,这是访问成功的状态
可以通过respone.context
属性查看返回的数据
可以通过response.content
属性查看返回的html结构
使用reverse生成url
当然这里同样可以使用reverse
函数生成url
>>> from django.urls import reverse
>>> response = client.get(reverse("polls:index"))
视图测试准备
现在产品部门对IndexView提出了要求,显示最近的5条数据,但是不能包含未来的数据
开发人员接到需求后,将对IndexView进行修改
修改IndexView
.......
def get_queryset(self):
# return Question.objects.all()
return Question.objects.order_by('-pub_date')[:5]
在这里,不再使用all()提取全部数据,而是首先使用order_by进行日期倒序排序,然后截取5条
编写测试用例
打开newpolls/test.py进行编辑
首先添加一个函数create_question
def create_question(question_text, days):
time = timezone.now() + datetime.timedelta(days=days)
return Question.objects.create(question_text=question_text, pub_date=time)
在执行测试的时候,调用这个函数创建测试数据
测试数据示例
测试过程将向数据库添加一些数据
create_question(question_text="你喜欢C++吗", 0)
create_question(question_text="你喜欢Java吗", 0)
create_question(question_text="你喜欢Python吗",0)
create_question(question_text="你喜欢编程吗", 30)
其中包含了一条在未来的数据"你喜欢编程吗"
测试空数据
第一条用例用于测试当Question表中没有数据时的响应是否正确
class QuestionIndexViewTests(TestCase):
def test_no_questions(self):
response = self.client.get(reverse('newpolls:index'))
# 检查status_code == 200, 不能出现其他状态码
self.assertEqual(response.status_code, 200)
# 检查响应内容是否包含了 "No polls are available."提示消息
self.assertContains(response, "No polls are available.")
# 检查数据是否为空
self.assertQuerysetEqual(response.context['question_list'], [])
assertEqual
- 比较两个值是否相等
assertContains
- 检测是否包含字符串片段
assertQuerysetEqual
- 检查数据集是否相等
编写完毕后,清空数据表进行测试
No.1 在这个用例里面进行了三项测试,我们会发现遇到了
“AssertionError: False is not true : Couldn't find 'No polls are available.' in response”
这是因为我们没有在index.html中添加提示'No polls are available.'
打开模版newpolls/templates/newpolls/index.html
{% if question_list %}
{% for question in question_list %}
<li>
<a href="{% url 'newpolls:detail' question.id %}">{{ question.question_text }}</a>
<br>
<a href="{% url 'newpolls:result' question.id %}">查看结果</a>
</li>
{% endfor %}
{% else %}
<li>No polls are available.</li>
{% endif %}
重新测试,该项就会通过了
测试存在的数据
该用例将创建一条pub_date在过去某个日期的Question,然后检测数据是否正常显示
def test_past_question(self):
question = create_question(question_text="Past question.", days=-30)
response = self.client.get(reverse('newpolls:index'))
self.assertQuerysetEqual(
response.context['question_list'],
[question],
)
运行测试, 测试通过
测试未来的数据
未来的数据根据要求不应该显示出来
def test_future_question(self):
create_question(question_text="Future question.", days=30)
response = self.client.get(reverse('newpolls:index'))
self.assertQuerysetEqual(response.context['question_list'], [])
self.assertContains(response, "No polls are available.")
由于视图存在bug,会将未来的数据也显示出来
修复显示未来数据的bug
打开newpolls/views.py 在视图中通过添加过滤器来加载正确的数据
class IndexView(ListView):
.....
def get_queryset(self):
# return Question.objects.all()
# return Question.objects.order_by('-pub_date')[:5]
return Question.objects.filter(pub_date__lte=timezone.now()
).order_by('-pub_date')[:5]
在新的代码中使用了filter过滤器,pub_date__lte=timezone.now() 表示数据检索条件为pub_date < timezone.now()
修复后再次运行测试通过
现在我们修复了bug,可以大胆的进行更多测试
同时测试新旧数据
def test_future_question_and_past_question(self):
question = create_question(question_text="Past question.", days=-30)
create_question(question_text="Future question.", days=30)
response = self.client.get(reverse('newpolls:index'))
self.assertQuerysetEqual(
response.context['question_list'],
[question],
)
毫无疑问,我们这次没有新的bug,一次性通过了
测试多条数据
不过我们还是不放心,如果显示多条数据是否正常,毕竟刚才都是仅仅显示一条数据
创建新的用例
def test_two_past_questions(self):
question1 = create_question(question_text="Past question 1.", days=-30)
question2 = create_question(question_text="Past question 2.", days=-5)
response = self.client.get(reverse('newpolls:index'))
self.assertQuerysetEqual(
response.context['question_list'],
[question2, question1],
)
毫无疑问,一次性通过,为自己鼓掌吧
测试详情视图
详情视图测试用例大同小异,这里需要传入pk_id
class QuestionDetailViewTests(TestCase):
def test_future_question(self):
future_question = create_question(question_text='Future question.', days=5)
url = reverse('newpolls:detail', args=(future_question.id,))
response = self.client.get(url)
self.assertEqual(response.status_code, 404)
def test_past_question(self):
past_question = create_question(question_text='Past Question.', days=-5)
url = reverse('newpolls:detail', args=(past_question.id,))
response = self.client.get(url)
self.assertContains(response, past_question.question_text)
第一个测试用例没有通过,是因为我们的detail视图存在bug
修复detail视图bug
class DetailView(DjangoDetail):
......
# 新增部分代码,重写get_queryset
def get_queryset(self):
return Question.objects.filter(pub_date__lte=timezone.now())
再次运行测试,通过
新人注意
可以看到随着功能越来越多,要求越来越复杂,测试用例也会越来越多,这时候需要有效管理自己的测试代码,建议有以下几点
- 每个模型或视图都有一个单独的测试用例
- 每组条件都有一个单独测试方法
- 测试方法名称应该能体现测试对象的功能描述
- 因为很多项目做不到这些,他们放弃了测试用例编写,这也是现实
讨论区