鍍金池/ 教程/ Python/ 編寫你的第一個Django應(yīng)用,第5部分
點擊劫持保護(hù)
安全問題歸檔
Model 類參考
將遺留數(shù)據(jù)庫整合到Django
關(guān)聯(lián)對象參考
內(nèi)建基于類的視圖的API
聚合
Django 中的用戶認(rèn)證
django.contrib.humanize
Django管理文檔生成器
分頁
使用Django輸出CSV
加密簽名
文件儲存API
安全
Django中的測試
國際化和本地化
為Django編寫首個補(bǔ)丁
條件表達(dá)式
日志
模型元選項
部署靜態(tài)文件
執(zhí)行查詢
使用Django認(rèn)證系統(tǒng)
基于類的視圖
中間件
編寫自定義的django-admin命令
Django 的設(shè)置
格式本地化
數(shù)據(jù)庫訪問優(yōu)化
錯誤報告
基于類的內(nèi)建通用視圖
編寫自定義存儲系統(tǒng)
編寫你的第一個 Django 程序 第3部分
編寫數(shù)據(jù)庫遷移
使用表單
編寫你的第一個 Django 程序 第2部分
編寫你的第一個 Django 程序 第1部分
如何使用會話
系統(tǒng)檢查框架
新手入門
信號
編寫視圖
如何使用WSGI 部署
編寫你的第一個Django應(yīng)用,第6部分
常見的網(wǎng)站應(yīng)用工具
Widgets
內(nèi)建的視圖
模型實例參考
視圖層
Django中的密碼管理
高級教程:如何編寫可重用的應(yīng)用
國際化和本地化
"本地特色"附加功能
TemplateResponse 和 SimpleTemplateResponse
模式編輯器
文件上傳
快速安裝指南
部署 Django
表單 API
表單素材 ( <code>Media</code> 類)
管理文件
其它核心功能
查找 API 參考
表單
Admin
數(shù)據(jù)庫函數(shù)
自定義查找
使用基于類的視圖處理表單
管理操作
開發(fā)過程
編寫你的第一個Django應(yīng)用,第5部分
進(jìn)行原始的sql查詢
模型層
多數(shù)據(jù)庫
編寫你的第一個 Django 程序 第4部分
Django安全
Django 初探
Django異常
重定向應(yīng)用
按需內(nèi)容處理
管理器
視圖裝飾器
驗證器
使用Django輸出PDF
File對象
Django 的快捷函數(shù)
基于類的通用視圖 —— 索引
為模型提供初始數(shù)據(jù)
模板層
URL調(diào)度器
中間件
模型

編寫你的第一個Django應(yīng)用,第5部分

本教程上接教程第4部分。 我們已經(jīng)建立一個網(wǎng)頁投票應(yīng)用,現(xiàn)在我們將為它創(chuàng)建一些自動化測試。

自動化測試簡介

什么是自動化測試?

測試是檢查你的代碼是否正常運行的簡單程序。

測試可以劃分為不同的級別。 一些測試可能專注于小細(xì)節(jié)(某一個模型的方法是否會返回預(yù)期的值?), 其他的測試可能會檢查軟件的整體運行是否正常(用戶在對網(wǎng)站進(jìn)行了一系列的操作后,是否返回了正確的結(jié)果?)。這些其實和你早前在教程 1中做的差不多, 使用shell來檢測一個方法的行為,或者運行程序并輸入數(shù)據(jù)來檢查它的行為方式。

自動化測試的不同之處就在于這些測試會由系統(tǒng)來幫你完成。你創(chuàng)建了一組測試程序,當(dāng)你修改了你的應(yīng)用,你就可以用這組測試程序來檢查你的代碼是否仍然同預(yù)期的那樣運行,而無需執(zhí)行耗時的手動測試。

為什么你需要創(chuàng)建測試

那么,為什么要創(chuàng)建測試?而且為什么是現(xiàn)在?

你可能感覺學(xué)習(xí)Python/Django已經(jīng)足夠,再去學(xué)習(xí)其他的東西也許需要付出巨大的努力而且沒有必要。 畢竟,我們的投票應(yīng)用已經(jīng)活蹦亂跳了; 將時間運用在自動化測試上還不如運用在改進(jìn)我們的應(yīng)用上。 如果你學(xué)習(xí)Django就是為了創(chuàng)建一個投票應(yīng)用,那么創(chuàng)建自動化測試顯然沒有必要。 但如果不是這樣,現(xiàn)在是一個很好的學(xué)習(xí)機(jī)會。

測試將節(jié)省你的時間

在某種程度上, ‘檢查起來似乎正常工作’將是一種令人滿意的測試。 在更復(fù)雜的應(yīng)用中,你可能有幾十種組件之間的復(fù)雜的相互作用。

這些組件的任何一個小的變化,都可能對應(yīng)用的行為產(chǎn)生意想不到的影響。 檢查起來‘似乎正常工作’可能意味著你需要運用二十種不同的測試數(shù)據(jù)來測試你代碼的功能,僅僅是為了確保你沒有搞砸某些事 —— 這不是對時間的有效利用。

尤其是當(dāng)自動化測試只需要數(shù)秒就可以完成以上的任務(wù)時。 如果出現(xiàn)了錯誤,測試程序還能夠幫助找出引發(fā)這個異常行為的代碼。

有時候你可能會覺得編寫測試程序?qū)⒛銖挠袃r值的、創(chuàng)造性的編程工作里帶出,帶到了單調(diào)乏味、無趣的編寫測試中,尤其是當(dāng)你的代碼工作正常時。

然而,比起用幾個小時的時間來手動測試你的程序,或者試圖找出代碼中一個新引入的問題的原因,編寫測試程序還是令人愜意的。

測試不僅僅可以發(fā)現(xiàn)問題,它們還能防止問題

將測試看做只是開發(fā)過程中消極的一面是錯誤的。

沒有測試,應(yīng)用的目的和意圖將會變得相當(dāng)模糊。 甚至在你查看自己的代碼時,也不會發(fā)現(xiàn)這些代碼真正干了些什么。

測試改變了這一切; 它們使你的代碼內(nèi)部變得明晰,當(dāng)錯誤出現(xiàn)后,它們會明確地指出哪部分代碼出了問題 —— 甚至你自己都不會料到問題會出現(xiàn)在那里。

測試使你的代碼更受歡迎

你可能已經(jīng)創(chuàng)建了一個堪稱輝煌的軟件,但是你會發(fā)現(xiàn)許多其他的開發(fā)者會由于它缺少測試程序而拒絕查看它一眼;沒有測試程序,他們不會信任它。 Jacob Kaplan-Moss,Django最初的幾個開發(fā)者之一,說過“不具有測試程序的代碼是設(shè)計上的錯誤?!?/p>

你需要開始編寫測試的另一個原因就是其他的開發(fā)者在他們認(rèn)真研讀你的代碼前可能想要查看一下它有沒有測試。

測試有助于團(tuán)隊合作

之前的觀點是從單個開發(fā)人員來維護(hù)一個程序這個方向來闡述的。 復(fù)雜的應(yīng)用將會被一個團(tuán)隊來維護(hù)。 測試能夠減少同事在無意間破壞你的代碼的機(jī)會(和你在不知情的情況下破壞別人的代碼的機(jī)會)。 如果你想在團(tuán)隊中做一個好的Django開發(fā)者,你必須擅長測試!

基本的測試策略

編寫測試有很多種方法。

一些開發(fā)者遵循一種叫做“由測試驅(qū)動的開發(fā)”的規(guī)則;他們在編寫代碼前會先編好測試。 這似乎與直覺不符,盡管這種方法與大多數(shù)人經(jīng)常的做法很相似:人們先描述一個問題,然后創(chuàng)建一些代碼來解決這個問題。 由測試驅(qū)動的開發(fā)可以用Python測試用例將這個問題簡單地形式化。

更常見的情況是,剛接觸測試的人會先編寫一些代碼,然后才決定為這些代碼創(chuàng)建一些測試。 也許在之前就編寫一些測試會好一點,但什么時候開始都不算晚。

有時候很難解決從什么地方開始編寫測試。 如果你已經(jīng)編寫了數(shù)千行Python代碼,挑選它們中的一些來進(jìn)行測試不會是太容易的。 這種情況下,在下次你對代碼進(jìn)行變更,或者添加一個新功能或者修復(fù)一個bug時,編寫你的第一個測試,效果會非常好。

現(xiàn)在,讓我們馬上來編寫一個測試。

編寫我們的第一個測試

我們找出一個錯誤

幸運的是,polls應(yīng)用中有一個小錯誤讓我們可以馬上來修復(fù)它:如果Question在最后一個天發(fā)布,Question.was_published_recently() 方法返回True(這是對的),但是如果Question的pub_date 字段是在未來,它還返回True(這肯定是不對的)。

你可以在管理站點中看到這一點; 創(chuàng)建一個發(fā)布時間在未來的一個Question; 你可以看到Question 的變更列表聲稱它是最近發(fā)布的。

你還可以使用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

由于將來的事情并不能稱之為‘最近’,這確實是一個錯誤。

創(chuàng)建一個測試來暴露這個錯誤

我們需要在自動化測試?yán)镒龅暮蛣偛旁趕hell里做的差不多,讓我們來將它轉(zhuǎn)換成一個自動化測試。

應(yīng)用的測試用例安裝慣例一般放在該應(yīng)用的tests.py文件中;測試系統(tǒng)將自動在任何以test開頭的文件中查找測試用例。

將下面的代碼放入polls應(yīng)用下的tests.py文件中:

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() 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.assertEqual(future_question.was_published_recently(), False)

我們在這里做的是創(chuàng)建一個django.test.TestCase子類,它具有一個方法可以創(chuàng)建一個pub_date在未來的Question實例。然后我們檢查was_published_recently()的輸出 —— 它應(yīng)該是 False.

運行測試

在終端中,我們可以運行我們的測試:

$ python manage.py test polls

你將看到類似下面的輸出:

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.assertEqual(future_question.was_published_recently(), False)
AssertionError: True != False

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

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

發(fā)生了如下這些事:

  • python manage.py test polls查找polls 應(yīng)用下的測試用例
  • 它找到 django.test.TestCase 類的一個子類
  • 它為測試創(chuàng)建了一個特定的數(shù)據(jù)庫
  • 它查找用于測試的方法 —— 名字以test開始
  • 它運行test_was_published_recently_with_future_question創(chuàng)建一個pub_date為未來30天的 Question實例
  • ... 然后利用assertEqual()方法,它發(fā)現(xiàn)was_published_recently() 返回True,盡管我們希望它返回False

這個測試通知我們哪個測試失敗,甚至是錯誤出現(xiàn)在哪一行。

修復(fù)這個錯誤

我們已經(jīng)知道問題是什么:Question.was_published_recently() 應(yīng)該返回 False,如果它的pub_date是在未來。在models.py中修復(fù)這個方法,讓它只有當(dāng)日期是在過去時才返回True :

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'...
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

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

在找出一個錯誤之后,我們編寫一個測試來暴露這個錯誤,然后在代碼中更正這個錯誤讓我們的測試通過。

未來,我們的應(yīng)用可能會出許多其它的錯誤,但是我們可以保證我們不會無意中再次引入這個錯誤,因為簡單地運行一下這個測試就會立即提醒我們。 我們可以認(rèn)為這個應(yīng)用的這一小部分會永遠(yuǎn)安全了。

更加綜合的測試

在這里,我們可以使was_published_recently() 方法更加穩(wěn)定;事實上,在修復(fù)一個錯誤的時候引入一個新的錯誤將是一件很令人尷尬的事。

在同一個類中添加兩個其它的測試方法,來更加綜合地測試這個方法:

polls/tests.py
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.assertEqual(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.assertEqual(recent_question.was_published_recently(), True)

現(xiàn)在我們有三個測試來保證無論發(fā)布時間是在過去、現(xiàn)在還是未來 Question.was_published_recently()都將返回合理的數(shù)據(jù)。

再說一次,polls 應(yīng)用雖然簡單,但是無論它今后會變得多么復(fù)雜以及會和多少其它的應(yīng)用產(chǎn)生相互作用,我們都能保證我們剛剛為它編寫過測試的那個方法會按照預(yù)期的那樣工作。

測試一個視圖

這個投票應(yīng)用沒有區(qū)分能力:它將會發(fā)布任何一個Question,包括 pub_date字段位于未來。我們應(yīng)該改進(jìn)這一點。 設(shè)定pub_date在未來應(yīng)該表示Question在此刻發(fā)布,但是直到那個時間點才會變得可見。

視圖的一個測試

當(dāng)我們修復(fù)上面的錯誤時,我們先寫測試,然后修改代碼來修復(fù)它。 事實上,這是由測試驅(qū)動的開發(fā)的一個簡單的例子,但做的順序并不真的重要。

在我們的第一個測試中,我們專注于代碼內(nèi)部的行為。 在這個測試中,我們想要通過瀏覽器從用戶的角度來檢查它的行為。

在我們試著修復(fù)任何事情之前,讓我們先查看一下我們能用到的工具。

Django測試客戶端

Django提供了一個測試客戶端來模擬用戶和代碼的交互。我們可以在tests.py 甚至在shell 中使用它。

我們將再次以shell開始,但是我們需要做很多在tests.py中不必做的事。首先是在 shell中設(shè)置測試環(huán)境:

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

setup_test_environment()安裝一個模板渲染器,可以使我們來檢查響應(yīng)的一些額外屬性比如response.context,否則是訪問不到的。請注意,這種方法不會建立一個測試數(shù)據(jù)庫,所以以下命令將運行在現(xiàn)有的數(shù)據(jù)庫上,輸出的內(nèi)容也會根據(jù)你已經(jīng)創(chuàng)建的Question不同而稍有不同。

下一步我們需要導(dǎo)入測試客戶端類(在之后的tests.py 中,我們將使用django.test.TestCase類,它具有自己的客戶端,將不需要導(dǎo)入這個類):

>>> 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.core.urlresolvers import reverse
>>> response = client.get(reverse('polls:index'))
>>> response.status_code
200
>>> response.content
'\n\n\n    <p>No polls are available.</p>\n\n'
>>> # note - you might get unexpected results if your ``TIME_ZONE``
>>> # in ``settings.py`` is not correct. If you need to change it,
>>> # you will also need to restart your shell session
>>> from polls.models import Question
>>> from django.utils import timezone
>>> # create a Question and save it
>>> q = Question(question_text="Who is your favorite Beatle?", pub_date=timezone.now())
>>> q.save()
>>> # check the response once again
>>> response = client.get('/polls/')
>>> response.content
'\n\n\n    <ul>\n    \n        <li><a href="/polls/1/">Who is your favorite Beatle?</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']
[<Question: Who is your favorite Beatle?>]

改進(jìn)我們的視圖

投票的列表顯示還沒有發(fā)布的投票(即pub_date在未來的投票)。讓我們來修復(fù)它。

在教程 4中,我們介紹了一個繼承ListView的基于類的視圖:

polls/views.py
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]

response.context_data['latest_question_list'] 取出由視圖放置在context 中的數(shù)據(jù)。

我們需要修改get_queryset方法并讓它將日期與timezone.now()進(jìn)行比較。首先我們需要添加一行導(dǎo)入:

polls/views.py
from django.utils import timezone

然后我們必須像這樣修改get_queryset方法:

polls/views.py
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]

Question.objects.filter(pub_date__lte=timezone.now()) 返回一個查詢集,包含pub_date小于等于timezone.now的Question。

測試我們的新視圖

啟動服務(wù)器、在瀏覽器中載入站點、創(chuàng)建一些發(fā)布時間在過去和將來的Questions ,然后檢驗只有已經(jīng)發(fā)布的Question會展示出來,現(xiàn)在你可以對自己感到滿意了。你不想每次修改可能與這相關(guān)的代碼時都重復(fù)這樣做 —— 所以讓我們基于以上shell會話中的內(nèi)容,再編寫一個測試。

將下面的代碼添加到polls/tests.py:

polls/tests.py
from django.core.urlresolvers import reverse

我們將創(chuàng)建一個快捷函數(shù)來創(chuàng)建Question,同時我們要創(chuàng)建一個新的測試類:

polls/tests.py
def create_question(question_text, days):
    """
    Creates a question with the given `question_text` 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.",
                            status_code=200)
        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.>']
        )

讓我們更詳細(xì)地看下以上這些內(nèi)容。

第一個是Question的快捷函數(shù)create_question,將重復(fù)創(chuàng)建Question的過程封裝在一起。

test_index_view_with_no_questions不創(chuàng)建任何Question,但會檢查消息“No polls are available.” 并驗證latest_question_list為空。注意django.test.TestCase類提供一些額外的斷言方法。在這些例子中,我們使用assertContains() 和 assertQuerysetEqual()。

在test_index_view_with_a_past_question中,我們創(chuàng)建一個Question并驗證它是否出現(xiàn)在列表中。

在test_index_view_with_a_future_question中,我們創(chuàng)建一個pub_date 在未來的Question。數(shù)據(jù)庫會為每一個測試方法進(jìn)行重置,所以第一個Question已經(jīng)不在那里,因此首頁面里不應(yīng)該有任何Question。

等等。 事實上,我們是在用測試模擬站點上的管理員輸入和用戶體驗,檢查針對系統(tǒng)每一個狀態(tài)和狀態(tài)的新變化,發(fā)布的是預(yù)期的結(jié)果。

測試 DetailView

一切都運行得很好; 然而,即使未來發(fā)布的Question不會出現(xiàn)在index中,如果用戶知道或者猜出正確的URL依然可以訪問它們。所以我們需要給DetailView添加一個這樣的約束:

polls/views.py
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())

當(dāng)然,我們將增加一些測試來檢驗pub_date 在過去的Question 可以顯示出來,而pub_date在未來的不可以:

polls/tests.py
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)
        response = self.client.get(reverse('polls:detail',
                                   args=(future_question.id,)))
        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)
        response = self.client.get(reverse('polls:detail',
                                   args=(past_question.id,)))
        self.assertContains(response, past_question.question_text,
                            status_code=200)

更多的測試思路

我們應(yīng)該添加一個類似get_queryset的方法到ResultsView并為該視圖創(chuàng)建一個新的類。這將與我們剛剛創(chuàng)建的非常類似;實際上將會有許多重復(fù)。

我們還可以在其它方面改進(jìn)我們的應(yīng)用,并隨之不斷增加測試。例如,發(fā)布一個沒有Choices的Questions就顯得傻傻的。所以,我們的視圖應(yīng)該檢查這點并排除這些 Questions。我們的測試應(yīng)該創(chuàng)建一個不帶Choices 的 Question然后測試它不會發(fā)布出來, 同時創(chuàng)建一個類似的帶有 Choices的Question 并驗證它會 發(fā)布出來。

也許登陸的用戶應(yīng)該被允許查看還沒發(fā)布的 Questions,但普通游客不行。 再說一次:無論添加什么代碼來完成這個要求,需要提供相應(yīng)的測試代碼,無論你是否是先編寫測試然后讓這些代碼通過測試,還是先用代碼解決其中的邏輯然后編寫測試來證明它。

從某種程度上來說,你一定會查看你的測試,然后想知道是否你的測試程序過于臃腫,這將我們帶向下面的內(nèi)容:

測試越多越好

看起來我們的測試代碼的增長正在失去控制。 以這樣的速度,測試的代碼量將很快超過我們的應(yīng)用,對比我們其它優(yōu)美簡潔的代碼,重復(fù)毫無美感。

沒關(guān)系。讓它們繼續(xù)增長。最重要的是,你可以寫一個測試一次,然后忘了它。 當(dāng)你繼續(xù)開發(fā)你的程序時,它將繼續(xù)執(zhí)行有用的功能。

有時,測試需要更新。 假設(shè)我們修改我們的視圖使得只有具有Choices的 Questions 才會發(fā)布。在這種情況下,我們許多已經(jīng)存在的測試都將失敗 —— 這會告訴我們哪些測試需要被修改來使得它們保持最新,所以從某種程度上講,測試可以自己照顧自己。

在最壞的情況下,在你的開發(fā)過程中,你會發(fā)現(xiàn)許多測試現(xiàn)在變得冗余。 即使這樣,也不是問題;對測試來說,冗余是一件好 事。

只要你的測試被合理地組織,它們就不會變得難以管理。 從經(jīng)驗上來說,好的做法是:

  • 每個模型或視圖具有一個單獨的TestClass
  • 為你想測試的每一種情況建立一個單獨的測試方法
  • 測試方法的名字可以描述它們的功能

進(jìn)一步的測試

本教程只介紹了一些基本的測試。 還有很多你可以做,有許多非常有用的工具可以隨便使用來你實現(xiàn)一些非常聰明的做法。

例如,雖然我們的測試覆蓋了模型的內(nèi)部邏輯和視圖發(fā)布信息的方式,你可以使用一個“瀏覽器”框架例如Selenium來測試你的HTML文件在瀏覽器中真實渲染的樣子。 這些工具不僅可以讓你檢查你的Django代碼的行為,還能夠檢查你的JavaScript的行為。 它會啟動一個瀏覽器,并開始與你的網(wǎng)站進(jìn)行交互,就像有一個人在操縱一樣,非常值得一看! Django 包含一個LiveServerTestCase來幫助與Selenium 這樣的工具集成。

如果你有一個復(fù)雜的應(yīng)用,你可能為了實現(xiàn)continuous integration,想在每次提交代碼后對代碼進(jìn)行自動化測試,讓代碼自動 —— 至少是部分自動 —— 地來控制它的質(zhì)量。

發(fā)現(xiàn)你應(yīng)用中未經(jīng)測試的代碼的一個好方法是檢查測試代碼的覆蓋率。 這也有助于識別脆弱的甚至死代碼。 如果你不能測試一段代碼,這通常意味著這些代碼需要被重構(gòu)或者移除。 Coverage將幫助我們識別死代碼。 查看與coverage.py 集成來了解更多細(xì)節(jié)。

Django 中的測試有關(guān)于測試更加全面的信息。

下一步?

關(guān)于測試的完整細(xì)節(jié),請查看Django 中的測試。

當(dāng)你對Django 視圖的測試感到滿意后,請閱讀本教程的第6部分來 了解靜態(tài)文件的管理。

譯者:Django 文檔協(xié)作翻譯小組,原文:Part 5: Testing。

本文以 CC BY-NC-SA 3.0 協(xié)議發(fā)布,轉(zhuǎn)載請保留作者署名和文章出處。

Django 文檔協(xié)作翻譯小組人手緊缺,有興趣的朋友可以加入我們,完全公益性質(zhì)。交流群:467338606。

下一篇:模型