Python Web开发-Django2.0学习07

本文介绍了如何在Django项目中实施自动化测试,包括编写单元测试、使用测试客户端模拟用户交互等,确保应用功能的稳定性和可靠性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

七、自动化测试

在开发中,我们都需要测试代码的正确性。前面的例子都是我们写好代码后,运行开发服务器,在浏览器上自己点击测试,看写的代码是否正常,但是这样做很麻烦,因为以后如果有改动,可能会影响以前本来正常的功能,这样以前的功能又得测试一遍,非常不方便,Django中有完善的单元测试,我们可以对开发的每一个功能进行单元测试,这样只要运行一个命令 python manage.py test,就可以测试功能是否正常。

测试就是检查代码是否按照自己的预期那样运行。自动化测试的不同之处在于测试工作是由系统为您完成的。您只需创建一组测试,然后在对应用程序进行更改时,可以检查代码是否仍然按照您的初始设计工作,而不必执行耗时的手动测试。

测试驱动开发: 有时候,我们知道自己需要的功能(结果),并不知道代码如何书写,这时候就可以利用测试驱动开发(Test Driven Development),先写出我们期待得到的结果(把测试代码先写出来),再去完善代码,直到不报错,我们就完成了。

1、shell测试

在我们的投票应用程序中,有一个小错误,在polls/models.py中,Question.was_published_recently() 函数是用于判断是否是过去最近一天内发表的,当实际上,并没有完全考虑到。对于在将来发表的,还是返回True。我们可以在项目环境终端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

2、开始自动化测试

我们在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):
        """
        was_published_recently() returns 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)

在cmd中,进入到项目目录,输入下面语句开始测试:

python manage.py test polls
//python manage.py test polls.tests.QuestionModelTests

可以看到:

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 "/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'...

可以看到是File “/path/to/mysite/polls/tests.py”, line 16, in test_was_published_recently_with_future_question这里出了问题,查看上下文,发现return值那里判断不完整,修改polls/models.py代码如下:

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.001s

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

我们可以进行更多的测试,在polls/tests.py输入以下代码进行测试:

def test_was_published_recently_with_old_question(self):
    """
    was_published_recently() returns False for questions whose pub_date
    is 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_date
    is 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)

3、使用测试客户端

Django提供了一个测试Client模拟用户在视图级别与代码进行交互。我们可以在tests.py或者shell中使用它。我们先在shell中使用它,在那里我们需要做一些在tests.py中不必要的事情。首先是建立测试环境,代码如下:

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

接着我们需要导入Client类,但在tests.py中我们使用django.test.TestCase替代,代码如下:

>>> from django.test import Client
>>> #为客户端创建一个实例供我们使用
>>> client = Client()

开始测试:

>>> # 从'/'获取响应
>>> response = client.get('/')
Not Found: /
>>> # 我们应该从这个地址得到一个404; 如果你看到一个
>>> # "Invalid HTTP_HOST header" 错误和一个400响应,你可能
>>> # 省略了前面描述的setup_test_environment()调用。
>>> response.status_code
404
>>> # 另一方面,我们应该期望找到'/polls/' 
>>> # 我们将使用'reverse()'
>>> 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'
>>> response.context['latest_question_list']
<QuerySet [<Question: What's up?>]>

可以发现,这里跟前面一样,都会获得将来发表的questions。

4、测试ListView

修改polls/views.py的IndexView,代码如下:

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]

根据上面的shell会话创建一个测试,在polls/tests.py增加代码,如下:

from django.urls import reverse

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


class QuestionIndexViewTests(TestCase):
    def test_no_questions(self):
        """
        如果不存在问题,则显示适当的消息。
        """
        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):
        ""
        过去发布的问题,显示在索引页上。
        """
        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.>']
        )

5、测试DetailView

同样,先修改polls/views.py的DetailView,代码如下:

class DetailView(generic.DetailView):
    ...
    def get_queryset(self):
        """
        排除尚未发布的问题。
        """
        return Question.objects.filter(pub_date__lte=timezone.now())

在polls/tests.py增加代码,如下:

class QuestionDetailViewTests(TestCase):
    def test_future_question(self):
        """
        如果问题尚未发布,返回404未找到。
        """
        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):
        """
        如果是过去发布的问题,显示question_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)

想了解更多关于自动化测试,请查阅Testing in Django


参考资料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

森森向上

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值