django入门-测试-part5

尊重作者的劳动,转载请注明作者及原文地址 http://www.cnblogs.com/txwsqk/p/6515996.html 

完全翻译自官方文档 https://docs.djangoproject.com/en/1.10/intro/tutorial05/

前面好几段文字都是介绍什么是自动化测试,我们为什么需要写测试用例,还有现在流行的"测试驱动开发",总之有如下好处:

  • 节省你的时间
    你修改了工程,设计好多模块,如果没有自动化测试,好吧,手动一个个测吧
  • 测试不能发现问题,但是能阻止问题的发生
    没有测试用例,那么你程序的行为就是不可预见的,即使这是你写的代码,你实际也不知道它内部是怎么运行的;有了测试用例,当某一个地方出问题,它就能指出这部分有问题,即使你自己压根没有意识到这里有问题
  • 测试可以让你的代码更有吸引力
    当你写了一段很牛[A-Z][1]的代码,但是别人并不想看,因为你没有测试用例,那么别人就认为你的代码是不可信的
  • 测试让团队合作更愉快
    小的应用可能只有一个开发者,但是大部分复杂的应用是一个团队一起开发的,测试用例可以防止你的同事不小心干扰了你的代码

好了,总之就是为你的工程写测试 百利无一害 

在前面章节中我们是怎么验证代码的,通过django-admin的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

好了下面我们用django自带的测试功能--自动化测试

在你的应用目录下有一个 test.py  就是它

django的自动化测试系统会在你的应用目录下找test开头的文件然后运行它

我们往test.py里写个测试用例

import datetime

from django.utils import timezone
from django.test import TestCase

from .models import Question


class QuestionMethodTests(TestCase):

    def test_was_published_recently_with_future_question(self):
        """
        was_published_recently() should return False for questions whose
        pub_date is 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

运行这个命令都发生了什么呢

这个类继承了TestCase, 它会创建一个测试用的数据库,这个类的方法都应该是test开头的方法

运行结果如下

Creating test database for alias 'default'...
F
======================================================================
FAIL: test_was_published_recently_with_future_question (polls.tests.QuestionMethodTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/path/to/mysite/polls/tests.py", line 16, in test_was_published_recently_with_future_question
    self.assertIs(future_question.was_published_recently(), False)
AssertionError: True is not False

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (failures=1)
Destroying test database for alias 'default'...

显然测试失败了

我们创建了一个问题,这个问题的发布时间是30天后,那么我们显然希望was_published_recently()返回False

但是测试用例却返回了True,所以我们代码有bug,我们修复一下polls/models.py

def was_published_recently(self):
    now = timezone.now()
    return now - datetime.timedelta(days=1) <= self.pub_date <= now

在重新执行下 

python manage.py test polls

好了这回测试通过了

Creating test database for alias 'default'...
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK
Destroying test database for alias 'default'...

现在我们修复了 was_published_recently()的bug,我们来加几个复杂的测试用例

def test_was_published_recently_with_old_question(self):
    """
    was_published_recently() should return False for questions whose
    pub_date is older than 1 day.
    """
    time = timezone.now() - datetime.timedelta(days=30)
    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() should return True for questions whose
    pub_date is within the last day.
    """
    time = timezone.now() - datetime.timedelta(hours=1)
    recent_question = Question(pub_date=time)
    self.assertIs(recent_question.was_published_recently(), True)

下面我们来测试视图函数

我们在创建question时,如果pub_date是未来的时间,那么按理说这不应该在页面显示的,就像京东明天12:00有抢购活动,那么在这个时间点之前是不应该让用户看到的

在继续讲之前,先介绍一个django的测试命令 django.test.Client, 它可以在视图层模拟用户的交互行为,我们可以用Client命令在test.py或shell中

>>> from django.test.utils import setup_test_environment
>>> setup_test_environment()

这个命令会加载模板渲染功能,这样我们就可以检验一些额外的属性比如response.context

注意这个命令不会去创建测试数据库,它用真实的数据库数据做测试

还有不要忘了在settings.py里设置正确的时区TIME_ZONE

Client怎么用呢

>>> from django.test import Client
>>> # create an instance of the client for our use
>>> client = Client()
>>> # get a response from '/' >>> response = client.get('/') >>> # we should expect a 404 from that address >>> response.status_code 404 >>> # 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&#39;s up?</a></li>\n \n </ul>\n\n' >>> # If the following doesn't work, you probably omitted the call to >>> # setup_test_environment() described above >>> response.context['latest_question_list'] <QuerySet [<Question: What's up?>]>

好了回到正题,修复我们的index视图,之前的index视图是这样的,没有判断question的发布时间是否是小于现在的时间

class IndexView(generic.ListView):
    template_name = 'polls/index.html'
    context_object_name = 'latest_question_list'

    def get_queryset(self):
        """Return the last five published questions."""
        return Question.objects.order_by('-pub_date')[:5]

修正这个问题,当pub_date小于现在的时间时不显示

def get_queryset(self):
    """
    Return the last five published questions (not including those set to be
    published in the future).
    """
    return Question.objects.filter(
        pub_date__lte=timezone.now()
    ).order_by('-pub_date')[:5]

来测试一下这个新视图吧

def create_question(question_text, days):
    """
    Creates a question with the given `question_text` and published the
    given number of `days` offset to now (negative for questions published
    in the past, positive for questions that have yet to be published).
    """
    time = timezone.now() + datetime.timedelta(days=days)
    return Question.objects.create(question_text=question_text, pub_date=time)


class QuestionViewTests(TestCase):
    def test_index_view_with_no_questions(self):
        """
        If no questions exist, an appropriate message should be 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_index_view_with_a_past_question(self):
        """
        Questions with a pub_date in the past should be displayed on the
        index page.
        """
        create_question(question_text="Past question.", days=-30)
        response = self.client.get(reverse('polls:index'))
        self.assertQuerysetEqual(
            response.context['latest_question_list'],
            ['<Question: Past question.>']
        )

    def test_index_view_with_a_future_question(self):
        """
        Questions with a pub_date in the future should not be displayed on
        the 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_index_view_with_future_question_and_past_question(self):
        """
        Even if both past and future questions exist, only past questions
        should be displayed.
        """
        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: Past question.>']
        )

    def test_index_view_with_two_past_questions(self):
        """
        The questions index page may display multiple questions.
        """
        create_question(question_text="Past question 1.", days=-30)
        create_question(question_text="Past question 2.", days=-5)
        response = self.client.get(reverse('polls:index'))
        self.assertQuerysetEqual(
            response.context['latest_question_list'],
            ['<Question: Past question 2.>', '<Question: Past question 1.>']
        )

讲解:

create_question()函数让我们方便的创建question

上面的几个函数根据函数名就能知道是干什么的,不解释了

 

index视图修复完了,detail视图也得改

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())

一样的逻辑,当pub_date less than equeal 小于等于timezone.now()现在的时间则不显示

为detail写测试用例

class QuestionIndexDetailTests(TestCase):
    def test_detail_view_with_a_future_question(self):
        """
        The detail view of a question with a pub_date in the future should
        return 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_detail_view_with_a_past_question(self):
        """
        The detail view of a question with a pub_date in the past should
        display 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)

继续总结

你会发现当你开始写测试用例,你的测试代码就疯狂的增加了,而且他们很多重复的内容,相比你写的代码,测试代码是那么的丑陋,这不要紧,因为大部分时间你都不会察觉到他们的存在,你可以继续你的开发任务,不过当你修改了视图逻辑,你也要同步更新你的测试用例,不然会有一大顿测试用例过不了

关于写测试用例,下面有几条好的建议:

  • 一个模型或视图一个单独的测试类
  • 每个测试的方法只测试一种情况
  • 测试方法的名字要顾名思义

扩展一下

我们现在只介绍了基本的测试功能,当你想测试浏览器的行为时可以使用 "Selenium"测试框架,这个框架不光能测试你的代码,还有你的javascript,django也包含了一个工具LiveServerTestCase来让你使用Selenium

当你的功能很复杂时,你可能希望当你提交代码时能自动运行测试,那么你去关注一下"持续集成"相关的内容

现在很多ide可以检查你的代码覆盖率,用这个方法也能找出你的哪些代码还没有被测试到,这也能让你发现哪些代码已经没用了可以删掉了.

如果你发现有的代码无法被测试,很明显这段代码应该被重构或者删除.有个工具Coverage可以帮你识别无用的代码 参考https://docs.djangoproject.com/en/1.10/topics/testing/advanced/#topics-testing-code-coverage

posted @ 2017-03-07 17:46  txwsqk  阅读(1485)  评论(0编辑  收藏  举报