• Django 2.0 学习(08):Django 自动化测试


    编写我们的第一个测试

    确定bug
    幸运的是,在polls应用中存在一个小小的bug急需修复:无论Question的发布日期是最近(最后)的日期,还是将来很多天的日期,Question.was_published_recently()方法都会返回True。使用下面的代码对其进行验证:

    >>> import datetime
    >>>
    >>> from django.utils import timezone
    >>> from polls.models import Question
    >>> future_question = Question(pub_date=timezone.now() + datetime.timedelta(days=30))
    >>> future_question.was_published_recently()
    True
    

    然而,将来的事情不可能是最近(最后)的,这是明显的错误。

    创建测试显示bug
    我们刚刚在shell中完成的测试问题正是我们在自动化测试中要做的,因此让我们转入自动化测试。按照惯例,对应用的测试就是在应用的tests.py文件中,测试系统会自动查找以test开始的任意文件。在polls应用中编辑tests.py文件,代码如下:

    import datetime
    from django.utils import timezone
    from django.test import TestCase
    
    from .models import Question
    
    
    class QuestionModelTests(TestCase):
        def test_was_published_recently_with_future_question(self):
            time = timezone.now() + datetime.timedelta(days=30)
            future_question = Question(pub_date=time)
            self.assertIs(future_question.was_published_recently(), False)
    

    这里我们所做的是创建一个django.test.TestCase子类,该子类只有一个方法:使用pub_date创建一个Question实例。检查was_published_recently()的输出,应该是False。

    运行测试
    在终端中,运行测试代码:

    python manage.py test polls
    

    我们将会看到下面的输出内容:

    Creating test database for alias 'default'...
    System check identified no issues (0 silenced).
    F
    ======================================================================
    FAIL: test_was_published_recently_with_future_question (polls.tests.QuestionModelTests)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "E:mysitepolls	ests.py", line 12, 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'...
    

    我们来分析下,发生了什么:

    • python manage.py test polls命令在polls应用中查找测试;
    • 查找到django.test.TestCase子类;
    • 为目标测试创建一个专门的数据库;
    • 查找一个名字以test开始的方法;
    • test_was_published_recently_with_future_question方法中,创建一个Question实例,该实例的pub_date字段是将来的30天;
    • 最后,assertIs()方法发现was_published_recently()返回True,而我们想要的返回是False

    测试通知我们哪个测试失败了,并且失败产生在代码的哪行。

    修复bug
    我们已经知道问题是:Question.was_published_recently()在其参数pub_date是将来的日期时会返回False。修改models.py文件中的方法,让它在日期是过去的情况下也能返回True,其代码如下:

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

    再次运行测试命令,会看到如下所示:

    Creating test database for alias 'default'...
    System check identified no issues (0 silenced).
    .
    ----------------------------------------------------------------------
    Ran 1 test in 0.000s
    
    OK
    Destroying test database for alias 'default'...
    

    更全面的测试
    在这里,我们可以进一步约束was_published_recently()方法;实际上,在修复bug时引进其他bug是非常尴尬的事情。在相同的类中,我们增加两个方法来全面的测试方法的行为,编辑文件polls/tests.py代码如下:

    import datetime
    from django.utils import timezone
    from django.test import TestCase
    
    from .models import Question
    
    
    class QuestionModelTests(TestCase):
        def test_was_published_recently_with_future_question(self):
            time = timezone.now() + datetime.timedelta(days=30)
            future_question = Question(pub_date=time)
            self.assertIs(future_question.was_published_recently(), False)
    
        def test_was_published_recently_with_old_question(self):
            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):
            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)
    

    现在有三个测试方法来证实Question.was_published_recently()对过去、现在和将来的返回值。

    测试视图

    Django测试客户端
    Django提供了一个测试客户端,用来模仿用户在视图级别与我们的代码互动。我们可以在test.py中使用它,也可以在shell中使用。我们将在shell中来进行测试,首先启动shell并在其中设置测试环境:

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

    setup_test_environment()安装一个模板渲染器,它可以检查响应的某些附加属性,比如:response.context;否则附加属性将不可用,但是该方法不会设置测试数据库。

    接下来,我们需要导入测试客户端类:

    >>> from django.test import Client
    >>> client = Client() // 创建一个client实例,供我们接下来使用
    

    准备好之后,我们就可以利用client做些测试工作:

    >>> response = client.get('/')
    Not Found: /
    >>> response.status_code
    404
    >>> from django.urls import reverse
    >>> response = client.get(reverse('polls:index'))
    >>> response.status_code
    200
    >>> response.content
    b'
        <ul>
            
                <li>
                    <a href="/polls/1/">What is up old?</a>
    
      </li>
            
        </ul>
    '
    >>> response.context['latest_question_list']
    <QuerySet [<Question: What is up old?>]>
    

    改进视图函数
    打开polls/views.py文件,添加代码如下:

    from django.utils import timezone
    
    class IndexView(generic.ListView):
        template_name = 'polls/index.html'
        context_object_name = 'latest_question_list'
    
        def get_queryset(self):
            return Question.objects.filter(pub_date__lte=timezone.now()).order_by('-pub_date')[:5]
    

    Question.objects.filter(pub_date__lte=timezone.now()) 返回一个Questions查询集合,该集合是pub_date小于或等于timezone.now的数据。

    现在我们基于test.py文件进行新的测试,打开polls/test.py文件,添加如下代码:

    from django.urls import reverse
    
    def create_question(question_text, days):
        time = timezone.now() + datetime.timedelta(days=days)
        return Question.objects.create(question_text=question_text, pub_date=time)
    
    
    class QuestionIndexViewTests(TestCase):
        def test_no_questions(self):
            response = self.client.generic(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):
            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_future_question(self):
            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):
            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_two_past_questions(self):
            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公共(快捷)方法,用来创建问题,主要是在其他程序中被重复调用;
    • test_no_question没有创建任何问题,只是检查信息No polls are available"",并且确认latest_question_list是否为空。django.test.TestCase类提供了额外的断言方法,上面的例子中就用到asserContains()assertQuerysetEqual()这两个方法;
    • test_past_question中,我们创建了一个问题并且确认该问题会出现在问题列表中;
    • test_future_question中,我们创建了一个发布日期是将来的问题。对于每个测试方法,数据库都会被重置,所以第一个问题不会存在在数据库中,并且在index中不会由任何问题;

    测试DetailView
    我们做的已经很好了,尽管将来的问题没有显示在index页面中,但是如果用户知道正确的URL或者猜到它们,用户依然能够访问到它们。所以,我们需要对DetailView视图添加相似的约束,依然打开polls/views.py文件,修改代码如下:

    class DetailView(generic.DetailView):
        model = Question
        template_name = 'polls/detail.html'
    
        def get_queryset(self):
            return Question.objects.filter(pub_date__lte=timezone.now())
    

    同理,我们将会添加一些测试,去检查每个问题:如果发布日期是过去的,就显示;如果发布日期是将来的,就不显示;打开polls/tests.py文件,添加下面代码:

    class QuestionDetailViewTests(TestCase):
        def test_future_question(self):
            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):
            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)
    

    好的测试规则应该遵循:

    • 对于每个模型或者视图,编写独立的(测试类)TestClass
    • 对于每个测试条件编写独立的测试方法;
    • 可以通过测试方法的名字理解其功能;
  • 相关阅读:
    IOS遍历未知对象属性、函数
    [Unity3D]Unity3D游戏开发之Logo渐入渐出效果的实现
    面向画布(Canvas)的JavaScript库
    将canvas画布内容转化为图片(toDataURL(),创建url)
    canvas上的像素操作(图像复制,细调)
    【bzoj1251】序列终结者(伸展树)
    延时标记
    曼哈顿距离(坐标投影距离之和)d(i,j)=|X1-X2|+|Y1-Y2|.
    曼哈顿距离最小生成树与莫队算法(总结)
    莫队算法(区间处理)
  • 原文地址:https://www.cnblogs.com/love9527/p/8651930.html
Copyright © 2020-2023  润新知