Django 官方文档write your first Django app --5

Writing your first Django app, part 5

自动化测试入门

什么是自动化测试?
测试时检查你代码运行状况的常规手段。

测试操作层次有很大区别,有些可能需要处理一些极小的细节(比如特定model方法是否返回期待值?),或是检查软件的所有操作途径(一连串的用户输入是否会返还正确的值?),你可以手动在shell里面检测,输出数据看它是否按相应情况做出更改。

自动化测试有哪些不同呢?你创造一组测试,当你改变你的应用时,你可以检查一下代码是不是像你最初想的一样运行,不需要浪费时间去手动测试。


为什么你需要创建测试?

你可能觉得这不太重要,反正我们的投票应用运行的很好。如果这个应用是你做的最后一个Django程序,那么你可以不学做测试,反之现在是个学习的好机会。

测试能节省你的时间

现在的测试都很简单,大抵是’检查xx是否工作‘之类的。但是在一个复杂的应用里,它的组件间会有很多复杂的交互影响。

对一个组件进行变更会发生一连串意想不到的影响。这时候测试代码可能要测试多大20个不同的变量和返回值看它们是否没有奔溃。

自动化测试更适合这个工作,当哪个玩意发生故障了,自动化测试会协助你找出代码。

有时候测试像个零工琐事,把你从创造力十足的编程工作中拉开去写噪杂和无聊的测试,特别是你的代码运行良好时。

然而,写测试这个任务可比你自己手动测试问题出在哪里要好得多。。。


测试不只是识别问题,更能预防它们

仅仅思考测试的负面是一个大错误

没有测试的话,应用的作用可能会更加晦涩。即使是你自己写的代码,有时你也会不明白它在做什么。

测试可以从内部阐明你的代码,当发生错误时,这部分代码的测试就会报错。


测试能让你的代码更具吸引力

你可能创造了一个很棒的软件,但是如果没有测试的话,你会发现其它的开发者都懒得去它。没有测试,他们不会相信它。‘Jacob Kaplan-Moss’-Django创始人之一 - 曾经说过‘ 没有测试的代码是设计上的败笔 ‘


测试能有助于团队工作

复杂的应用通常是由团队维护的,测试能让你的同僚不会因为疏忽而去破坏你的代码。如果你想靠Django谋一份工作,你必须善于写测试


基础的测试策略

有很多写测试的方法途径。

一些程序员遵循“test-driven development”原则;他们在写代码之前就先写好了测试。

多数情况下,一个新手会先写些代码再决定写些测试。

很多时候,测试都不知道从哪里下手。当然如果你写过几千行的Python代码,做出这个选择会很容易。在这个应用里,可以改变一点东西,然后你就会发现一个bug。


写你的首个测试

我们发现一个bug

Question.was_published_recently()方法里,它判断Question是否是一天之内发布的,如果是就返回True。但是如果Question的pub_date值是未来的某一天呢?

$ python manage.py shell
>>> # 我们创建一个Question实例,把它的pub_date设为30天后
>>> future_question = Question(pub_date=timezone.now() + datetime.timedelta(days=30))
>>> # 还回是最近发布吗?
>>> future_question.was_published_recently()
True

创建一个测试来暴露这个bug

惯例是把应用的测试放在各自应用目录下的tests.ph文件里;测试系统会自动寻找以test开头的文件。

polls/tests.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() 当使用未来日期对象的时候,
        应该会返回False 
        """
        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

然后你会看到报错,发生了什么呢:

  • python manage.py test polls找到polls 应用里的测试程序
  • 它找到一个django.test.TestCase 的子类
  • 它为这次测试创建了一个临时的特殊数据库
  • 它搜索测试方法 – 名字以‘test’开头的方法
  • test_wat_published_recently_with_future_question它创建了一个Question实例,为pub_date域传入了30天以后的日期
  • 最后使用了assertIs()方法。它发现was_published_recently()返回True,但是它预期是False

修复这个bug

我们已经知道问题出在哪里,现在去修改代码:

polls/models.py:

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

再一次运行test:Ran 1 test in 0.001s,一切ok

我们的应用可能之后还会发生很多错误,但我们不会再疏忽让这个bug出现,因为只要运行测试就马上会发出警告,我们可以考虑应用的这小部分是安全的了。


更多综合性的测试

在这个测试类下面加入另外两个测试方法

polls/tests.py:

def test_was_published_recently_with_old_question(self):
    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):
    time = timezone.now() - datetime.timedelta(hours-1)
    recent_question = Question(pub_date=time)
    self.assertIs(recent_question.was_published_recently(), True)

测试View

polls应用还没有鉴别力:它会发布几个question,而不会注意到它们的pub_date。我们应该改进一下,让pub_date为未来的question暂时不可见

一个view测试

当我们修改上个bug时,我们首先写了个测试然后修改了bug。事实上这就是个简单的’test-driven development’例子。

在我们的首个测试中,我们关注的是代码内部的行为。但是这个测试里,我们希望像用户使用浏览器一样的举动。

当我们开始之前,先看看有哪些工具可以使用。


Django test client

Django提供了client来模拟用户交互。我们可以在tests.py或者shell里使用它。

但是在shell里面,我们需要建立起必须的测试环境:

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

setup_test_enviroment()装载了模版渲染器,它准许检查response的属性,比如response.context.记住这个方法不会建立一个test数据库。

接下来,我们需要引用测试的client类(之后在test.py里面我们将会用django.test.TestCase类,它也有属于自己的client,所有这不是必须的):

>>> from django.test import Client
>>> # 创建一个clent的实例供我们使用
>>> client = Client()

都准备好后,我们可以让client做些事情了:

>>> # 从 '/' 获得相应
>>> response = client.get('/')
>>> # 我们应该会得到一个 404错误
>>> 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' 
>>> # 如果还是没响应,你应该没有建立setup_test_environment()
>>> response.context['latest_question_list']
<QuerySet [<Question: What's up?>]>

改进我们的view

打开polls/views.py,我们需要修改get_queryset()方法,让它检查日期并且和当前时间做比较。

polls/views.py:

from django.utils import timezone

#...
def get_queryset(self):
    """ 返回五个发布的question(不包括时间是未来的question) """
    return Question.objects.filter(pub_date__lte=timezone.now()).order_by('-pub_date')[:5]

Question.objects.filter(pub_date__lte=timezone.now())返回一个包括pub_date少于或等于(也就是时间上早于或等于)现在的查询集合。lte == less than equal


测试我们的新view

创建一个test,以刚才的shell内容为基础

polls/tests.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 QuestionViewTests(TestCase):
    def test_index_view_with_no_question(self):
        response = self.client.get(reverse('polls:index'))
        self.assertEqual(response.suatus_code, 200)
        self.assertContain(response, 'No polls are available.')
        self.assertQuerysetEqual(response.context['latest_question_list'], [])

    def test_index_view_with_a_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_index_view_with_a_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_index_view_with_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_index_view_with_two_past_questions(self):
        create_question(question_text='Past question 1.',days=-1)
        create_question(question_text='Past question 2.', days=-30)
        response = self.client.get(reverse('polls:index'))
        self.assertQuerysetEqual(response.context['latest_question_list'], ['<Question: Past question 2.>', '<Question: Past question 1.>'])

首先创立一个快速建立question实例的函数,减少代码输入量。

test_index_view_with_no_questio没有创建question,所有检查response会发现’No polls are available.’,经过确认latest_question_list也确实是空的。记住django.test.TestCase类提供了很多抛出异常的assert方法。在这里我们用到了assertContains()assertQuerysetEqual()

test_index_view_with_a_past_question中,我们创建了一个question,并且确认它在list中。

test_index_view_with_a_future_question中,我们创建了一个pub_date在未来的question。测试数据库在每次测试都会重置,所以第一个question已经不在里面了,经确认list为空。


测试DatailView

即使日期为未来的question不出现在index,用户也可以自己靠猜的构造一个URL访问它,所以我们需要在DetailView添加相似的限制。

polls/views.py:

class DetailView(generic.DetailView):
    ...
    def get_queryset(self):
        return Question.objects.filter(pub_date__lte=timezone.now())

然后我们添加一些tests,检查detail页面,如果pub_date是以前的话就可以显示,如果是未来则不现实

polls/tests.py:

class QuestionIndexDetailTests(TestCase):
    def test_detail_view_with_a_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_detail_view_with_a_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)
        self.assertEqual(response.status_code, 200)

更多的测试想法

我们应该给ResultsView创建一个get_queryset方法,并且创建一个新的测试类,和上面的代码会有很大的重复。

我们也可以在其它方面改进我们的应用。比如检测没有发布Choice的Question,如果有则不显示。我们的测试会创造一个没有Choice的Question实例看看它有没有显示在网页上,或者我们可以创建与之相反的测试,创建一个有Choice的实例,测试它是否显示在页面上。

也许登录的admin用户可以看到未发布的Question。再次声明,为软件增加功能也需要增加响应的测试。


测试的时候,代码多就是好

看起来我们的测试代码可能臃肿的难以掌控,甚至测试代码比程序本身还多,代码重复也是不美观的。

但是这些都不重要,让他们臃肿。大多数情况,你都是写一个测试然后就忘了它了。但是只要你继续开发你的程序,它就会继续发挥自己的作用。

有时候,test也需要更新。假如你修改了View,使得只有Questions于Choices可以发布,万一发生这种情况,很多现存的tests就会失效。

为了让你的tests看起来更易于管理一些,可以养成下面这些好习惯

  • 每个model或者view都有一个分开的TestClass子类
  • 一个分离的test设置所有你想测试的情况
  • test方法名里面描述它的功能

进一步了解测试

这篇教学仅仅介绍了一些基础的测试。还有很多你可以做的,以及一些很有用的工具。

例如,你可以用内置浏览器框架Selenium来测试你的HTML是否成功渲染。Django内置的LiveServerTestCase整合了一些类似Selenium的工具。

如果你有个复杂的应用,你可能想每次提交时都自动测试,请看continuous integration

检查你的测试覆盖率,给帮你识别脆弱的死代码。如果你没法测试一片代码,通常意味着这些代码需要重构或删除。更多细节看integration with coverage.py

关于测试的综合信息在Testing in Django.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在 Django REST Framework 中创建一个新的应用程序,请按照以下步骤进行操作: 1. 在命令行中,使用以下命令创建一个新的 Django 应用程序: ``` python manage.py startapp myapp ``` 2. 在项目的 settings.py 文件中注册新的应用程序,将它添加到 INSTALLED_APPS 列表中: ``` INSTALLED_APPS = [ ... 'myapp', ] ``` 3. 在新的应用程序文件夹中,创建一个 serializers.py 文件来定义模型序列化器,例如: ```python from rest_framework import serializers from .models import MyModel class MyModelSerializer(serializers.ModelSerializer): class Meta: model = MyModel fields = '__all__' ``` 4. 在新的应用程序文件夹中,创建一个 views.py 文件来定义 API 视图,例如: ```python from rest_framework import viewsets from .models import MyModel from .serializers import MyModelSerializer class MyModelViewSet(viewsets.ModelViewSet): queryset = MyModel.objects.all() serializer_class = MyModelSerializer ``` 5. 在新的应用程序文件夹中,创建一个 urls.py 文件来定义应用程序路由,例如: ```python from django.urls import path, include from rest_framework import routers from .views import MyModelViewSet router = routers.DefaultRouter() router.register(r'mymodels', MyModelViewSet) urlpatterns = [ path('', include(router.urls)), ] ``` 6. 最后,在项目的 urls.py 文件中,将应用程序的路由添加到主路由中: ```python from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('api/', include('myapp.urls')), ] ``` 现在你可以通过访问 /api/mymodels 来查看你新创建的 API 视图。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值