请稍候,加载中....

No.16 视图测试

在自动化测试环节,我们已经了解了测试用例的意义,以及如何使用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())

再次运行测试,通过

新人注意

可以看到随着功能越来越多,要求越来越复杂,测试用例也会越来越多,这时候需要有效管理自己的测试代码,建议有以下几点

  • 每个模型或视图都有一个单独的测试用例
  • 每组条件都有一个单独测试方法
  • 测试方法名称应该能体现测试对象的功能描述
  • 因为很多项目做不到这些,他们放弃了测试用例编写,这也是现实

Python学习手册-