这篇教程从 教程4 结束的地方开始。已经构建了一个网络投票应用程序,现在将为其创建一些自动化测试。
1. 原因:
雅各布·卡普兰-莫斯(Jacob Kaplan-Moss),Django的原始开发者之一,说过:“没有测试的代码在设计上就是有缺陷的。”
2. Bug:
原始代码:
通过使用shell检查一个日期在未来的问题上的方法来确认这个bug:
python manage.py shell
>>> import datetime
>>> from django.utils import timezone
>>> from polls.models import Question
>>> # create a Question instance with pub_date 30 days in the future
>>> future_question = Question(pub_date=timezone.now() + datetime.timedelta(days=30))
>>> # was it published recently?
>>> future_question.was_published_recently()
True
由于未来的事情不算是“最近的”,这显然是不正确的。
将以下内容放入polls应用程序的tests.py文件中:
import datetime
from django.test import TestCase
from django.utils import timezone
from .models import Questionclass QuestionModelTests(TestCase):def test_was_published_recently_with_future_question(self):"""was_published_recently() returns False for questions whose pub_dateis in the future."""time = timezone.now() + datetime.timedelta(days=30)future_question = Question(pub_date=time)self.assertIs(future_question.was_published_recently(), False)
就像这样:
在终端上运行这个测试:
python manage.py test polls
结果:
2. 修复Bug:
修改代码为:
def was_published_recently(self):now = timezone.now()return now - datetime.timedelta(days=1) <= self.pub_date <= now
就像这样:
再次测试:
python manage.py test polls
结果:
添加更多的测试:
def test_was_published_recently_with_old_question(self):"""was_published_recently() returns False for questions whose pub_dateis older than 1 day."""time = timezone.now() - datetime.timedelta(days=1, seconds=1)old_question = Question(pub_date=time)self.assertIs(old_question.was_published_recently(), False)
def test_was_published_recently_with_recent_question(self):"""was_published_recently() returns True for questions whose pub_dateis within the last day."""time = timezone.now() - datetime.timedelta(hours=23, minutes=59, seconds=59)recent_question = Question(pub_date=time)self.assertIs(recent_question.was_published_recently(), True)
就像这样:
再次测试:
python manage.py test polls
结果:
4. view视图测试
1. 进入 Django Shell
首先,你需要进入 Django 的 shell 环境。在项目根目录下运行以下命令:
python manage.py shell
这将启动一个 Python 解释器,允许你与 Django 项目进行交互。
2. 设置测试环境
在 shell 中,导入并调用 setup_test_environment
函数,以便配置测试环境:
>>> from django.test.utils import setup_test_environment
>>> setup_test_environment()
这一步是必要的,它设置了测试环境,使得你可以进行测试而不会影响实际的数据。
3. 创建客户端实例
接下来,导入 Client
类并创建一个实例,用于模拟 HTTP 请求:
>>> from django.test import Client
>>> # create an instance of the client for our use
>>> client = Client()
Client
类是 Django 测试框架的一部分,用于模拟用户与你的应用的交互。
4. 发送请求并处理响应
访问不存在的页面
尝试访问根路径 /
,并检查响应状态码:
>>> # get a response from '/'
>>> response = client.get("/")
Not Found: /
>>> # we should expect a 404 from that address; if you instead see an
>>> # "Invalid HTTP_HOST header" error and a 400 response, you probably
>>> # omitted the setup_test_environment() call described earlier.
>>> response.status_code
404
预期会得到一个 404 状态码,表示页面未找到。
访问存在的页面
接下来,访问 /polls/
路径,并检查响应状态码和内容:
>>> # on the other hand we should expect to find something at '/polls/'
>>> # we'll use 'reverse()' rather than a hardcoded URL
>>> from django.urls import reverse
>>> response = client.get(reverse("polls:index"))
>>> response.status_code
200
>>> response.content
b'\n <ul>\n \n <li><a href="/polls/1/">What's up?</a></li>\n \n </ul>\n\n'
>>> response.context["latest_question_list"]
<QuerySet [<Question: What's up?>]>
- 使用
reverse()
函数来获取 URL,而不是硬编码 URL。 - 检查响应状态码,预期为 200,表示请求成功。
- 查看响应内容,这里是一个简单的 HTML 列表。
- 访问响应上下文中的
latest_question_list
,预期为一个包含问题的QuerySet
。
5. Indexview视图测试
class IndexView(generic.ListView):template_name = "polls/index.html"context_object_name = "latest_question_list"def get_queryset(self):"""返回最后五个发布的问卷。"""return Question.objects.order_by("-pub_date")[:5]
解释
IndexView
继承自generic.ListView
,用于展示问卷列表。template_name
指定了使用的模板文件。context_object_name
定义了在模板中使用的变量名。get_queryset
方法返回按发布日期排序的最后五个问卷。
修改 get_queryset 方法
from django.utils import timezonedef get_queryset(self):"""返回最后五个已发布的问卷(不包括那些设置为将来发布的问卷)。"""return Question.objects.filter(pub_date__lte=timezone.now()).order_by("-pub_date")[:5]
解释
- 引入
timezone
模块以获取当前时间。 - 修改
get_queryset
方法,只返回发布日期小于或等于当前时间的问卷。
create_question 函数
from django.urls import reverse
from datetime import datetimedef create_question(question_text, days):"""创建一个问卷,给定 `question_text` 并在当前时间基础上偏移 `days` 天发布(负数表示过去,正数表示未来)。"""time = timezone.now() + datetime.timedelta(days=days)return Question.objects.create(question_text=question_text, pub_date=time)
解释
- 引入
reverse
用于反向解析URL。 - 引入
datetime
模块用于处理时间。 create_question
函数用于创建问卷,可以指定问卷文本和发布时间偏移。
QuestionIndexViewTests 类
class QuestionIndexViewTests(TestCase):def test_no_questions(self):"""If no questions exist, an appropriate message is displayed."""response = self.client.get(reverse("polls:index"))self.assertEqual(response.status_code, 200)self.assertContains(response, "No polls are available.")self.assertQuerySetEqual(response.context["latest_question_list"], [])def test_past_question(self):"""Questions with a pub_date in the past are displayed on theindex page."""question = create_question(question_text="Past question.", days=-30)response = self.client.get(reverse("polls:index"))self.assertQuerySetEqual(response.context["latest_question_list"],[question],)def test_future_question(self):"""Questions with a pub_date in the future aren't displayed onthe index page."""create_question(question_text="Future question.", days=30)response = self.client.get(reverse("polls:index"))self.assertContains(response, "No polls are available.")self.assertQuerySetEqual(response.context["latest_question_list"], [])def test_future_question_and_past_question(self):"""Even if both past and future questions exist, only past questionsare displayed."""question = create_question(question_text="Past question.", days=-30)create_question(question_text="Future question.", days=30)response = self.client.get(reverse("polls:index"))self.assertQuerySetEqual(response.context["latest_question_list"],[question],)def test_two_past_questions(self):"""The questions index page may display multiple questions."""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("polls:index"))self.assertQuerySetEqual(response.context["latest_question_list"],[question2, question1],)
解释
QuestionIndexViewTests
类用于测试IndexView
的不同情况。test_no_questions
测试没有问卷时的情况。test_past_question
测试过去问卷的显示。test_future_question
测试未来问卷不显示。test_future_question_and_past_question
测试同时存在过去和未来问卷时的情况。test_two_past_questions
测试显示多个过去问卷的情况。
在终端运行:
python manage.py shell
结果为:
6. DetailView视图测试
修改DetailView:
class DetailView(generic.DetailView):...def get_queryset(self):"""Excludes any questions that aren't published yet."""return Question.objects.filter(pub_date__lte=timezone.now())
如图:
测试代码:
class QuestionDetailViewTests(TestCase):def test_future_question(self):"""The detail view of a question with a pub_date in the futurereturns a 404 not found."""future_question = create_question(question_text="Future question.", days=5)url = reverse("polls:detail", args=(future_question.id,))response = self.client.get(url)self.assertEqual(response.status_code, 404)def test_past_question(self):"""The detail view of a question with a pub_date in the pastdisplays the question's text."""past_question = create_question(question_text="Past Question.", days=-5)url = reverse("polls:detail", args=(past_question.id,))response = self.client.get(url)self.assertContains(response, past_question.question_text)
在终端运行:
python manage.py shell
结果显示如下:
参考链接:https://docs.djangoproject.com/en/5.1/intro/tutorial05/