在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

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

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

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

自動(dòng)化測(cè)試簡(jiǎn)介

什么是自動(dòng)化測(cè)試?

測(cè)試是檢查你的代碼是否正常運(yùn)行的簡(jiǎn)單程序。

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

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

為什么你需要?jiǎng)?chuàng)建測(cè)試

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

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

測(cè)試將節(jié)省你的時(shí)間

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

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

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

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

然而,比起用幾個(gè)小時(shí)的時(shí)間來(lái)手動(dòng)測(cè)試你的程序,或者試圖找出代碼中一個(gè)新引入的問(wèn)題的原因,編寫(xiě)測(cè)試程序還是令人愜意的。

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

將測(cè)試看做只是開(kāi)發(fā)過(guò)程中消極的一面是錯(cuò)誤的。

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

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

測(cè)試使你的代碼更受歡迎

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

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

測(cè)試有助于團(tuán)隊(duì)合作

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

基本的測(cè)試策略

編寫(xiě)測(cè)試有很多種方法。

一些開(kāi)發(fā)者遵循一種叫做“由測(cè)試驅(qū)動(dòng)的開(kāi)發(fā)”的規(guī)則;他們?cè)诰帉?xiě)代碼前會(huì)先編好測(cè)試。 這似乎與直覺(jué)不符,盡管這種方法與大多數(shù)人經(jīng)常的做法很相似:人們先描述一個(gè)問(wèn)題,然后創(chuàng)建一些代碼來(lái)解決這個(gè)問(wèn)題。 由測(cè)試驅(qū)動(dòng)的開(kāi)發(fā)可以用Python測(cè)試用例將這個(gè)問(wèn)題簡(jiǎn)單地形式化。

更常見(jiàn)的情況是,剛接觸測(cè)試的人會(huì)先編寫(xiě)一些代碼,然后才決定為這些代碼創(chuàng)建一些測(cè)試。 也許在之前就編寫(xiě)一些測(cè)試會(huì)好一點(diǎn),但什么時(shí)候開(kāi)始都不算晚。

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

現(xiàn)在,讓我們馬上來(lái)編寫(xiě)一個(gè)測(cè)試。

編寫(xiě)我們的第一個(gè)測(cè)試

我們找出一個(gè)錯(cuò)誤

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

你可以在管理站點(diǎn)中看到這一點(diǎn); 創(chuàng)建一個(gè)發(fā)布時(shí)間在未來(lái)的一個(gè)Question; 你可以看到Question 的變更列表聲稱(chēng)它是最近發(fā)布的。

你還可以使用shell看到這點(diǎn):

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

由于將來(lái)的事情并不能稱(chēng)之為‘最近’,這確實(shí)是一個(gè)錯(cuò)誤。

創(chuàng)建一個(gè)測(cè)試來(lái)暴露這個(gè)錯(cuò)誤

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

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

將下面的代碼放入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)

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

運(yùn)行測(cè)試

在終端中,我們可以運(yùn)行我們的測(cè)試:

$ python manage.py test polls

你將看到類(lèi)似下面的輸出:

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)用下的測(cè)試用例
  • 它找到 django.test.TestCase 類(lèi)的一個(gè)子類(lèi)
  • 它為測(cè)試創(chuàng)建了一個(gè)特定的數(shù)據(jù)庫(kù)
  • 它查找用于測(cè)試的方法 —— 名字以test開(kāi)始
  • 它運(yùn)行test_was_published_recently_with_future_question創(chuàng)建一個(gè)pub_date為未來(lái)30天的 Question實(shí)例
  • ... 然后利用assertEqual()方法,它發(fā)現(xiàn)was_published_recently() 返回True,盡管我們希望它返回False

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

修復(fù)這個(gè)錯(cuò)誤

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

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

再次運(yùn)行測(cè)試:

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

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

在找出一個(gè)錯(cuò)誤之后,我們編寫(xiě)一個(gè)測(cè)試來(lái)暴露這個(gè)錯(cuò)誤,然后在代碼中更正這個(gè)錯(cuò)誤讓我們的測(cè)試通過(guò)。

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

更加綜合的測(cè)試

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

在同一個(gè)類(lèi)中添加兩個(gè)其它的測(cè)試方法,來(lái)更加綜合地測(cè)試這個(gè)方法:

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)在我們有三個(gè)測(cè)試來(lái)保證無(wú)論發(fā)布時(shí)間是在過(guò)去、現(xiàn)在還是未來(lái) Question.was_published_recently()都將返回合理的數(shù)據(jù)。

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

測(cè)試一個(gè)視圖

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

視圖的一個(gè)測(cè)試

當(dāng)我們修復(fù)上面的錯(cuò)誤時(shí),我們先寫(xiě)測(cè)試,然后修改代碼來(lái)修復(fù)它。 事實(shí)上,這是由測(cè)試驅(qū)動(dòng)的開(kāi)發(fā)的一個(gè)簡(jiǎn)單的例子,但做的順序并不真的重要。

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

在我們?cè)囍迯?fù)任何事情之前,讓我們先查看一下我們能用到的工具。

Django測(cè)試客戶(hù)端

Django提供了一個(gè)測(cè)試客戶(hù)端來(lái)模擬用戶(hù)和代碼的交互。我們可以在tests.py 甚至在shell 中使用它。

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

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

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

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

>>> from django.test import Client
>>> # create an instance of the client for our use
>>> client = Client()

這些都做完之后,我們可以讓這個(gè)客戶(hù)端來(lái)為我們做一些事:

>>> # 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)我們的視圖

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

在教程 4中,我們介紹了一個(gè)繼承ListView的基于類(lèi)的視圖:

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()) 返回一個(gè)查詢(xún)集,包含pub_date小于等于timezone.now的Question。

測(cè)試我們的新視圖

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

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

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

我們將創(chuàng)建一個(gè)快捷函數(shù)來(lái)創(chuàng)建Question,同時(shí)我們要?jiǎng)?chuàng)建一個(gè)新的測(cè)試類(lèi):

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)容。

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

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

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

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

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

測(cè)試 DetailView

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

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)然,我們將增加一些測(cè)試來(lái)檢驗(yàn)pub_date 在過(guò)去的Question 可以顯示出來(lái),而pub_date在未來(lái)的不可以:

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)

更多的測(cè)試思路

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

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

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

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

測(cè)試越多越好

看起來(lái)我們的測(cè)試代碼的增長(zhǎng)正在失去控制。 以這樣的速度,測(cè)試的代碼量將很快超過(guò)我們的應(yīng)用,對(duì)比我們其它優(yōu)美簡(jiǎn)潔的代碼,重復(fù)毫無(wú)美感。

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

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

在最壞的情況下,在你的開(kāi)發(fā)過(guò)程中,你會(huì)發(fā)現(xiàn)許多測(cè)試現(xiàn)在變得冗余。 即使這樣,也不是問(wèn)題;對(duì)測(cè)試來(lái)說(shuō),冗余是一件好 事。

只要你的測(cè)試被合理地組織,它們就不會(huì)變得難以管理。 從經(jīng)驗(yàn)上來(lái)說(shuō),好的做法是:

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

進(jìn)一步的測(cè)試

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

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

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

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

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

下一步?

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

當(dāng)你對(duì)Django 視圖的測(cè)試感到滿(mǎn)意后,請(qǐng)閱讀本教程的第6部分來(lái) 了解靜態(tài)文件的管理。

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

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

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

下一篇:模型