自動化測試是什么椰憋??
測試波势,是用來檢查代碼正確性的一些簡單的程序如暖。
測試在不同的層次中都存在笆檀。有些測試只關(guān)注某個很小的細(xì)節(jié)(某個模型的某個方法的返回值是否滿足預(yù)期?)盒至,而另一些測試可能檢查對某個軟件的一系列操作(某一用戶輸入序列是否造成了預(yù)期的結(jié)果酗洒?)士修。其實這和我們在?教程第 2 部分,里做的并沒有什么不同樱衷,我們使用?shell?來測試某一方法的功能棋嘲,或者運行某個應(yīng)用并輸入數(shù)據(jù)來檢查它的行為。
真正不同的地方在于箫老,自動化?測試是由某個系統(tǒng)幫你自動完成的封字。當(dāng)你創(chuàng)建好了一系列測試,每次修改應(yīng)用代碼后耍鬓,就可以自動檢查出修改后的代碼是否還像你曾經(jīng)預(yù)期的那樣正常工作阔籽。你不需要花費大量時間來進(jìn)行手動測試。
為什么你需要寫測試?
但是牲蜀,為什么需要測試呢笆制?又為什么是現(xiàn)在呢?
你可能覺得學(xué) Python/Django 對你來說已經(jīng)很滿足了涣达,再學(xué)一些新東西的話看起來有點負(fù)擔(dān)過重并且沒什么必要在辆。畢竟,我們的投票應(yīng)用看起來已經(jīng)完美工作了度苔。寫一些自動測試并不能讓它工作的更好匆篓。如果寫一個投票應(yīng)用是你想用 Django 完成的唯一工作,那你確實沒必要學(xué)寫測試寇窑。但是如果你還想寫更復(fù)雜的項目鸦概,現(xiàn)在就是學(xué)習(xí)測試寫法的最好時機了。
測試將節(jié)約你的時間?
在某種程度上甩骏,能夠「判斷出代碼是否正常工作」的測試窗市,就稱得上是個令人滿意的了。在更復(fù)雜的應(yīng)用程序中饮笛,組件之間可能會有數(shù)十個復(fù)雜的交互咨察。
在更加復(fù)雜的應(yīng)用中,各種組件之間的交互可能會及其的復(fù)雜福青。改變其中某一組件的行為摄狱,也有可能會造成意想不到的結(jié)果。判斷「代碼是否正常工作」意味著你需要用大量的數(shù)據(jù)來完整的測試全部代碼的功能无午,以確保你的小修改沒有對應(yīng)用整體造成破壞——這太費時間了二蓝。
尤其是當(dāng)你發(fā)現(xiàn)自動化測試能在幾秒鐘之內(nèi)幫你完成這件事時,就更會覺得手動測試實在是太浪費時間了指厌。當(dāng)某人寫出錯誤的代碼時刊愚,自動化測試還能幫助你定位錯誤代碼的位置。
有時候你會覺得踩验,和富有創(chuàng)造性和生產(chǎn)力的業(yè)務(wù)代碼比起來鸥诽,編寫枯燥的測試代碼實在是太無聊了商玫,特別是當(dāng)你知道你的代碼完全沒有問題的時候。
然而牡借,編寫測試還是要比花費幾個小時手動測試你的應(yīng)用拳昌,或者為了找到某個小錯誤而胡亂翻看代碼要有意義的多。
測試不僅能發(fā)現(xiàn)錯誤钠龙,而且能預(yù)防錯誤?
「測試是開發(fā)的對立面」炬藤,這種思想是不對的。
如果沒有測試碴里,整個應(yīng)用的行為意圖會變得更加的不清晰沈矿。甚至當(dāng)你在看自己寫的代碼時也是這樣,有時候你需要仔細(xì)研讀一段代碼才能搞清楚它有什么用咬腋。
而測試的出現(xiàn)改變了這種情況羹膳。測試就好像是從內(nèi)部仔細(xì)檢查你的代碼,當(dāng)有些地方出錯時根竿,這些地方將會變得很顯眼——就算你自己沒有意識到那里寫錯了陵像。
測試使你的代碼更有吸引力?
你也許遇到過這種情況:你編寫了一個絕贊的軟件,但是其他開發(fā)者看都不看它一眼寇壳,因為它缺少測試醒颖。沒有測試的代碼不值得信任。 Django 最初開發(fā)者之一的 Jacob Kaplan-Moss 說過:“項目規(guī)劃時沒有包含測試是不科學(xué)的壳炎∨⑶福”
其他的開發(fā)者希望在正式使用你的代碼前看到它通過了測試,這是你需要寫測試的另一個重要原因冕广。
測試有利于團隊協(xié)作?
前面的幾點都是從單人開發(fā)的角度來說的疏日。復(fù)雜的應(yīng)用可能由團隊維護(hù)偿洁。測試的存在保證了協(xié)作者不會不小心破壞了了你的代碼(也保證你不會不小心弄壞他們的)撒汉。如果你想作為一個 Django 程序員謀生的話,你必須擅長編寫測試涕滋!
基礎(chǔ)測試策略?
有好幾種不同的方法可以寫測試睬辐。
一些開發(fā)者遵循 "測試驅(qū)動" 的開發(fā)原則,他們在寫代碼之前先寫測試宾肺。這種方法看起來有點反直覺溯饵,但事實上,這和大多數(shù)人日常的做法是相吻合的锨用。我們會先描述一個問題丰刊,然后寫代碼來解決它≡鲇担「測試驅(qū)動」的開發(fā)方法只是將問題的描述抽象為了 Python 的測試樣例啄巧。
更普遍的情況是寻歧,一個剛接觸自動化測試的新手更傾向于先寫代碼,然后再寫測試秩仆。雖然提前寫測試可能更好码泛,但是晚點寫起碼也比沒有強。
有時候很難決定從哪里開始下手寫測試澄耍。如果你才寫了幾千行 Python 代碼噪珊,選擇從哪里開始寫測試確實不怎么簡單。如果是這種情況齐莲,那么在你下次修改代碼(比如加新功能痢站,或者修復(fù) Bug)之前寫個測試是比較合理且有效的。
所以铅搓,我們現(xiàn)在就開始寫吧瑟押。
開始寫我們的第一個測試?
首先得有個 Bug?
幸運的是,我們的?polls?應(yīng)用現(xiàn)在就有一個小 bug 需要被修復(fù):我們的要求是如果 Question 是在一天之內(nèi)發(fā)布的星掰,?Question.was_published_recently()?方法將會返回?True?多望,然而現(xiàn)在這個方法在?Question?的?pub_date?字段比當(dāng)前時間還晚時也會返回 True(這是個 Bug)。
你能從管理頁面確認(rèn)這個 bug 真實存在氢烘。在?shell?創(chuàng)建一個發(fā)布日期是未來某天的投票怀偷,在投票列表里你會看到它被標(biāo)明為最近發(fā)布(published recently):
>>> importdatetime>>> fromdjango.utilsimporttimezone>>> frompolls.modelsimportQuestion>>> # 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
因為將來發(fā)生的是肯定不是最近發(fā)生的,所以代碼明顯是錯誤的播玖。
創(chuàng)建一個測試來暴露這個 bug?
我們剛剛在?shell?里做的測試也就是自動化測試應(yīng)該做的工作椎工。所以我們來把它改寫成自動化的吧。
按照慣例蜀踏,Django 應(yīng)用的測試應(yīng)該寫在應(yīng)用的?tests.py?文件里维蒙。測試系統(tǒng)會自動的在所有以?tests?開頭的文件里尋找并執(zhí)行測試代碼。
將下面的代碼寫入?polls?應(yīng)用里的?tests.py?文件內(nèi):
polls/tests.py
importdatetimefromdjango.testimportTestCasefromdjango.utilsimporttimezonefrom.modelsimportQuestionclassQuestionModelTests(TestCase):deftest_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)
我們創(chuàng)建了一個?django.test.TestCase?的子類果覆,并添加了一個方法颅痊,此方法創(chuàng)建一個?pub_date?時未來某天的?Question?實例。然后檢查它的?was_published_recently()?方法的返回值——它?應(yīng)該?是 False局待。
運行測試?
在終端中斑响,我們通過輸入以下代碼運行測試:
$ python manage.py test polls
你將會看到運行結(jié)果:
Creatingtestdatabaseforalias'default'...Systemcheckidentifiednoissues(0silenced).F======================================================================FAIL:test_was_published_recently_with_future_question(polls.tests.QuestionModelTests)----------------------------------------------------------------------Traceback(mostrecentcalllast):File"/path/to/mysite/polls/tests.py",line16,intest_was_published_recently_with_future_questionself.assertIs(future_question.was_published_recently(),False)AssertionError:TrueisnotFalse----------------------------------------------------------------------Ran1testin0.001sFAILED(failures=1)Destroyingtestdatabaseforalias'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?實例。
接著使用?assertls()?方法薛耻,發(fā)現(xiàn)?was_published_recently()?返回了?True营罢,而我們期望它返回?False。
測試系統(tǒng)通知我們哪些測試樣例失敗了饼齿,和造成測試失敗的代碼所在的行號饲漾。
修復(fù)這個 bug?
我們早已知道瘟滨,當(dāng)?pub_date?為未來某天時,?Question.was_published_recently()?應(yīng)該返回?False能颁。我們修改?models.py?里的方法杂瘸,讓它只在日期是過去式的時候才返回?True:
polls/models.py
defwas_published_recently(self):now=timezone.now()returnnow-datetime.timedelta(days=1)<=self.pub_date<=now
然后重新運行測試:
Creatingtestdatabaseforalias'default'...Systemcheckidentifiednoissues(0silenced)..----------------------------------------------------------------------Ran1testin0.001sOKDestroyingtestdatabaseforalias'default'...
發(fā)現(xiàn) bug 后,我們編寫了能夠暴露這個 bug 的自動化測試伙菊。在修復(fù) bug 之后败玉,我們的代碼順利的通過了測試。
將來镜硕,我們的應(yīng)用可能會出現(xiàn)其他的問題运翼,但是我們可以肯定的是,一定不會再次出現(xiàn)這個 bug兴枯,因為只要簡單的運行一遍測試血淌,就會立刻收到警告。我們可以認(rèn)為應(yīng)用的這一小部分代碼永遠(yuǎn)是安全的财剖。
更全面的測試?
我們已經(jīng)搞定一小部分了悠夯,現(xiàn)在可以考慮全面的測試?was_published_recently()?這個方法以確定它的安全性,然后就可以把這個方法穩(wěn)定下來了躺坟。事實上沦补,在修復(fù)一個 bug 時不小心引入另一個 bug 會是非常令人尷尬的。
我們在上次寫的類里再增加兩個測試咪橙,來更全面的測試這個方法:
polls/tests.py
deftest_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)deftest_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)
現(xiàn)在夕膀,我們有三個測試來確保?Question.was_published_recently()?方法對于過去,最近美侦,和將來的三種情況都返回正確的值产舞。
再次申明,盡管?polls?現(xiàn)在是個非常簡單的應(yīng)用菠剩,但是無論它以后成長到多么復(fù)雜易猫,要和其他代碼進(jìn)行怎樣的交互,我們都能保證進(jìn)行過測試的那些方法的行為永遠(yuǎn)是符合預(yù)期的赠叼。
測試視圖?
我們的投票應(yīng)用對所有問題都一視同仁:它將會發(fā)布所有的問題擦囊,也包括那些?pub_date?字段值是未來的問題违霞。我們應(yīng)該改善這一點嘴办。如果?pub_date?設(shè)置為未來某天,這應(yīng)該被解釋為這個問題將在所填寫的時間點才被發(fā)布买鸽,而在之前是不可見的涧郊。
針對視圖的測試?
為了修復(fù)上述 bug ,我們這次先編寫測試眼五,然后再去改代碼妆艘。事實上彤灶,這是一個簡單的「測試驅(qū)動」開發(fā)模式的實例批旺,但其實這兩者的順序不太重要汽煮。
在我們的第一個測試中搏熄,我們關(guān)注代碼的內(nèi)部行為暇赤。我們通過模擬用戶使用瀏覽器訪問被測試的應(yīng)用來檢查代碼行為是否符合預(yù)期。
在我們動手之前鞋囊,先看看需要用到的工具們止后。
Django 測試工具之 Client?
Django 提供了一個供測試使用的?Client?來模擬用戶和視圖層代碼的交互。我們能在?tests.py?甚至是?shell?中使用它溜腐。
我們依照慣例從?shell?開始,首先我們要做一些在?tests.py?里不是必須的準(zhǔn)備工作挺益。第一步是在?shell?中配置測試環(huán)境:
>>> fromdjango.test.utilsimportsetup_test_environment>>> setup_test_environment()
setup_test_environment()?提供了一個模板渲染器,允許我們?yōu)?responses 添加一些額外的屬性现恼,例如?response.context黍檩,未安裝此 app 無法使用此功能。注意刽酱,這個方法并?不會?配置測試數(shù)據(jù)庫,所以接下來的代碼將會在當(dāng)前存在的數(shù)據(jù)庫上運行润文,輸出的內(nèi)容可能由于數(shù)據(jù)庫內(nèi)容的不同而不同殿怜。如果你的?settings.py?中關(guān)于?TIME_ZONE?的設(shè)置不對头谜,你可能無法獲取到期望的結(jié)果。如果你之前忘了設(shè)置截驮,在繼續(xù)之前檢查一下。
然后我們需要導(dǎo)入?django.test.TestCase?類(在后續(xù)?tests.py?的實例中我們將會使用?django.test.TestCase?類涵妥,這個類里包含了自己的 client 實例坡锡,所以不需要這一步):
>>> fromdjango.testimportClient>>> # create an instance of the client for our use>>> client=Client()
搞定了之后,我們可以要求 client 為我們工作了:
>>> # get a response from '/'>>> response=client.get('/')Not Found: />>> # we should expect a 404 from that address; if you instead see an>>> # "Invalid HTTP_HOST header" error and a 400 response, you probably>>> # omitted the setup_test_environment() call described earlier.>>> response.status_code404>>> # on the other hand we should expect to find something at '/polls/'>>> # we'll use 'reverse()' rather than a hardcoded URL>>> fromdjango.urlsimportreverse>>> response=client.get(reverse('polls:index'))>>> response.status_code200>>> response.contentb'\n? ? <ul>\n? ? \n? ? ? ? <li><a href="/polls/1/">What's up?</a></li>\n? ? \n? ? </ul>\n\n'>>> response.context['latest_question_list']<QuerySet [<Question: What's up?>]>
改善視圖代碼?
現(xiàn)在的投票列表會顯示將來的投票(?pub_date?值是未來的某天)拳缠。我們來修復(fù)這個問題窟坐。
在?教程的第 4 部分?里绵疲,我們介紹了基于?ListView?的視圖類:
polls/views.py
classIndexView(generic.ListView):template_name='polls/index.html'context_object_name='latest_question_list'defget_queryset(self):"""Return the last five published questions."""returnQuestion.objects.order_by('-pub_date')[:5]
我們需要改進(jìn)?get_queryset()?方法,讓他它能通過將 Question 的 pub_data 屬性與?timezone.now()?相比較來判斷是否應(yīng)該顯示此 Question徙菠。首先我們需要一行 import 語句:
polls/views.py
fromdjango.utilsimporttimezone
然后我們把?get_queryset?方法改寫成下面這樣:
polls/views.py
defget_queryset(self):"""? ? Return the last five published questions (not including those set to be? ? published in the future).? ? """returnQuestion.objects.filter(pub_date__lte=timezone.now()).order_by('-pub_date')[:5]
Question.objects.filter(pub_date__lte=timezone.now())?returns a queryset containing?Questions whose?pub_date?is less than or equal to - that is, earlier than or equal to -?timezone.now.
測試新視圖?
啟動服務(wù)器郁岩、在瀏覽器中載入站點问慎、創(chuàng)建一些發(fā)布時間在過去和將來的?Questions?,然后檢驗只有已經(jīng)發(fā)布的?Questions?會展示出來如叼,現(xiàn)在你可以對自己感到滿意了笼恰。你不想每次修改可能與這相關(guān)的代碼時都重復(fù)這樣做?—— 所以讓我們基于以上?shell?會話中的內(nèi)容,再編寫一個測試逼龟。
將下面的代碼添加到?polls/tests.py?:
polls/tests.py
fromdjango.urlsimportreverse
然后我們寫一個公用的快捷函數(shù)用于創(chuàng)建投票問題猴仑,再為視圖創(chuàng)建一個測試類:
polls/tests.py
defcreate_question(question_text,days):"""? ? Create a question with the given `question_text` and 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)returnQuestion.objects.create(question_text=question_text,pub_date=time)classQuestionIndexViewTests(TestCase):deftest_no_questions(self):"""? ? ? ? If no questions exist, an appropriate message is 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'],[])deftest_past_question(self):"""? ? ? ? Questions with a pub_date in the past are 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.>'])deftest_future_question(self):"""? ? ? ? Questions with a pub_date in the future aren't 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.")self.assertQuerysetEqual(response.context['latest_question_list'],[])deftest_future_question_and_past_question(self):"""? ? ? ? Even if both past and future questions exist, only past questions? ? ? ? are 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.>'])deftest_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)容辽俗。
首先是一個快捷函數(shù)?create_question,它封裝了創(chuàng)建投票的流程榴捡,減少了重復(fù)代碼朱浴。
test_no_questions?方法里沒有創(chuàng)建任何投票翰蠢,它檢查返回的網(wǎng)頁上有沒有 "No polls are available." 這段消息和?latest_question_list?是否為空。注意到?django.test.TestCase?類提供了一些額外的 assertion 方法梁沧,在這個例子中廷支,我們使用了?assertContains()?和?assertQuerysetEqual()?。
在?test_past_question?方法中恋拍,我們創(chuàng)建了一個投票并檢查它是否出現(xiàn)在列表中施敢。
在?test_future_question?中,我們創(chuàng)建?pub_date?在未來某天的投票僵娃。數(shù)據(jù)庫會在每次調(diào)用測試方法前被重置悯许,所以第一個投票已經(jīng)沒了,所以主頁中應(yīng)該沒有任何投票瘩扼。
剩下的那些也都差不多垃僚。實際上,測試就是假裝一些管理員的輸入栽燕,然后通過用戶端的表現(xiàn)是否符合預(yù)期來判斷新加入的改變是否破壞了原有的系統(tǒng)狀態(tài)。
測試?DetailView?
我們的工作似乎已經(jīng)很完美了浴讯?不蔼啦,還有一個問題:就算在發(fā)布日期時未來的那些投票不會在目錄頁?index?里出現(xiàn),但是如果用戶知道或者猜到正確的 URL 奈籽,還是可以訪問到它們鸵赫。所以我們得在?DetailView?里增加一些約束:
polls/views.py
classDetailView(generic.DetailView):...defget_queryset(self):"""? ? ? ? Excludes any questions that aren't published yet.? ? ? ? """returnQuestion.objects.filter(pub_date__lte=timezone.now())
當(dāng)然,我們將增加一些測試來檢驗?pub_date?在過去的?Question?可以顯示出來勾拉,而?pub_date?在未來的不可以:
polls/tests.py
classQuestionDetailViewTests(TestCase):deftest_future_question(self):"""? ? ? ? The detail view of a question with a pub_date in the future? ? ? ? returns a 404 not found.? ? ? ? """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)deftest_past_question(self):"""? ? ? ? The detail view of a question with a pub_date in the past? ? ? ? displays the question's 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)
更多的測試思路?
我們應(yīng)該給?ResultsView?也增加一個類似的?get_queryset?方法藕赞,并且為它創(chuàng)建測試卖局。這和我們之前干的差不多,事實上批销,基本就是重復(fù)一遍染坯。
我們還可以從各個方面改進(jìn)投票應(yīng)用,但是測試會一直伴隨我們掀宋。比方說仲锄,在目錄頁上顯示一個沒有選項?Choices?的投票問題就沒什么意義。我們可以檢查并排除這樣的投票題镣奋。測試可以創(chuàng)建一個沒有選項的投票怀愧,然后檢查它是否被顯示在目錄上。當(dāng)然也要創(chuàng)建一個有選項的投票哈垢,然后確認(rèn)它確實被顯示了。
恩蛤奢,也許你想讓管理員能在目錄上看見未被發(fā)布的那些投票陶贼,但是普通用戶看不到拜秧。不管怎么說章郁,如果你想要增加一個新功能,那么同時一定要為它編寫測試聊替。不過你是先寫代碼還是先寫測試那就隨你了培廓。
在未來的某個時刻,你一定會去查看測試代碼泣港,然后開始懷疑:「這么多的測試不會使代碼越來越復(fù)雜嗎价匠?」。別著急坡氯,我們馬上就會談到這一點。
當(dāng)需要測試的時候徐矩,測試用例越多越好?
貌似我們的測試多的快要失去控制了滤灯。按照這樣發(fā)展下去曼玩,測試代碼就要變得比應(yīng)用的實際代碼還要多了窒百。而且測試代碼大多都是重復(fù)且不優(yōu)雅的,特別是在和業(yè)務(wù)代碼比起來的時候顷帖,這種感覺更加明顯。
但是這沒關(guān)系精肃!?就讓測試代碼繼續(xù)肆意增長吧。大部分情況下绪励,你寫完一個測試之后就可以忘掉它了。在你繼續(xù)開發(fā)的過程中停做,它會一直默默無聞地為你做貢獻(xiàn)的大莫。
但有時測試也需要更新。想象一下如果我們修改了視圖眉抬,只顯示有選項的那些投票懈凹,那么只前寫的很多測試就都會失敗介评。但這也明確地告訴了我們哪些測試需要被更新,所以測試也會測試自己寒瓦。
最壞的情況是坪仇,當(dāng)你繼續(xù)開發(fā)的時候,發(fā)現(xiàn)之前的一些測試現(xiàn)在看來是多余的喂很。但是這也不是什么問題,多做些測試也?不錯凌摄。
如果你對測試有個整體規(guī)劃漓帅,那么它們就幾乎不會變得混亂忙干。下面有幾條好的建議:
對于每個模型和視圖都建立單獨的?TestClass
每個測試方法只測試一個功能
給每個測試方法起個能描述其功能的名字
深入代碼測試?
在本教程中,我們僅僅是了解了測試的基礎(chǔ)知識劣摇。你能做的還有很多弓乙,而且世界上有很多有用的工具來幫你完成這些有意義的事钧惧。
舉個例子浓瞪,在上述的測試中,我們已經(jīng)從代碼邏輯和視圖響應(yīng)的角度檢查了應(yīng)用的輸出涂乌,現(xiàn)在你可以從一個更加 "in-browser" 的角度來檢查最終渲染出的 HTML 是否符合預(yù)期英岭,使用 Selenium 可以很輕松的完成這件事。這個工具不僅可以測試 Django 框架里的代碼罚勾,還可以檢查其他部分吭狡,比如說你的 JavaScript划煮。它假裝成是一個正在和你站點進(jìn)行交互的瀏覽器,就好像有個真人在訪問網(wǎng)站一樣器躏!Django 它提供了?LiveServerTestCase?來和 Selenium 這樣的工具進(jìn)行交互。
如果你在開發(fā)一個很復(fù)雜的應(yīng)用的話瞎疼,你也許想在每次提交代碼時自動運行測試壁畸,也就是我們所說的持續(xù)集成?continuous integration?,這樣就能實現(xiàn)質(zhì)量控制的自動化太抓,起碼是部分自動化令杈。
一個找出代碼中未被測試部分的方法是檢查代碼覆蓋率逗噩。它有助于找出代碼中的薄弱部分和無用部分。如果你無法測試一段代碼捶障,通常說明這段代碼需要被重構(gòu)或者刪除纲刀。想知道代碼覆蓋率和無用代碼的詳細(xì)信息示绊,查看文檔?Integration with coverage.py?獲取詳細(xì)信息。
文檔?Django 中的測試?里有關(guān)于測試的更多信息拌禾。
接下來要做什么盆耽??
如果你想深入了解測試,就去看?Django 中的測試?坝咐。
當(dāng)你已經(jīng)比較熟悉測試 Django 視圖的方法后析恢,就可以繼續(xù)閱讀?教程第 6 部分?,學(xué)習(xí)靜態(tài)文件管理的相關(guān)知識泽篮。