已經(jīng)同步到gitbook豌拙,想閱讀的請(qǐng)轉(zhuǎn)到gitbook: Django 1.10 中文文檔
Writing your first Django app, part 5?
This tutorial begins where Tutorial 4 left off. We’ve built a Web-poll application, and we’ll now create some automated tests for it.
緊接著教程4。我們已經(jīng)創(chuàng)建了一個(gè)投票的web應(yīng)用鳖链,現(xiàn)在我們?yōu)樗鼊?chuàng)建一些自動(dòng)化測試用例姆蘸。
Introducing automated testing?
自動(dòng)化測試簡介
What are automated tests??
什么是自動(dòng)化測試
Tests are simple routines that check the operation of your code.
測試是檢查你的代碼是否能正常運(yùn)行。
Testing operates at different levels. Some tests might apply to a tiny detail (does a particular model method return values as expected?) while others examine the overall operation of the software (does a sequence of user inputs on the site produce the desired result?). That’s no different from the kind of testing you did earlier in Tutorial 2, using theshell to examine the behavior of a method, or running the application and entering data to check how it behaves.
測試可以劃分為不同的級(jí)別芙委。 一些測試可能專注于小細(xì)節(jié)(某一個(gè)模型的方法是否會(huì)返回預(yù)期的值逞敷?), 其他的測試可能會(huì)檢查軟件的整體運(yùn)行是否正常(用戶在對(duì)網(wǎng)站進(jìn)行了一系列的操作后灌侣,是否返回了正確的結(jié)果推捐?)。這些其實(shí)和你早前在教程2做的差不多侧啼, 使用shell 來檢測一個(gè)方法的行為牛柒,或者運(yùn)行程序并輸入數(shù)據(jù)來檢查它的行為方式。
What’s different in automated tests is that the testing work is done for you by the system. You create a set of tests once, and then as you make changes to your app, you can check that your code still works as you originally intended, without having to perform time consuming manual testing.
自動(dòng)化測試的不同之處就在于這些測試會(huì)由系統(tǒng)來幫你完成痊乾。你創(chuàng)建了一組測試程序皮壁,當(dāng)你修改了你的應(yīng)用,你就可以用這組測試程序來檢查你的代碼是否仍然同預(yù)期的那樣運(yùn)行哪审,而無需執(zhí)行耗時(shí)的手動(dòng)測試蛾魄。
Why you need to create tests?
你為什么需要測試
So why create tests, and why now?
那么為什么要測試,而且是現(xiàn)在湿滓?
You may feel that you have quite enough on your plate just learning Python/Django, and having yet another thing to learn and do may seem overwhelming and perhaps unnecessary. After all, our polls application is working quite happily now; going through the trouble of creating automated tests is not going to make it work any better. If creating the polls application is the last bit of Django programming you will ever do, then true, you don’t need to know how to create automated tests. But, if that’s not the case, now is an excellent time to learn.
你可能感覺學(xué)習(xí)Python/Django已經(jīng)足夠滴须,再去學(xué)習(xí)其他的東西也許需要付出巨大的努力而且沒有必要。 畢竟叽奥,我們的投票應(yīng)用已經(jīng)運(yùn)行良好了扔水; 將時(shí)間運(yùn)用在自動(dòng)化測試上,也并不能改進(jìn)我們的應(yīng)用朝氓。 如果你學(xué)習(xí)Django就是為了創(chuàng)建一個(gè)投票應(yīng)用魔市,那么創(chuàng)建自動(dòng)化測試顯然沒有必要。 但如果不是這樣膀篮,現(xiàn)在是一個(gè)很好的學(xué)習(xí)機(jī)會(huì)嘹狞。
Tests will save you time?
測試將節(jié)省你的時(shí)間
Up to a certain point, ‘checking that it seems to work’ will be a satisfactory test. In a more sophisticated application, you might have dozens of complex interactions between components.
在某種程度上, ‘確認(rèn)它似乎在正常運(yùn)行’誓竿,對(duì)測試來說是一種令人滿意的事情。 在更復(fù)雜的應(yīng)用中谈截,你可能有幾十種組件之間的復(fù)雜的交互行為筷屡。
A change in any of those components could have unexpected consequences on the application’s behavior. Checking that it still ‘seems to work’ could mean running through your code’s functionality with twenty different variations of your test data just to make sure you haven’t broken something - not a good use of your time.
這些組件的任何一個(gè)小的變化涧偷,都可能對(duì)應(yīng)用造成意想不到的影響。 確定它‘似乎正常工作’可能意味著你需要運(yùn)用二十種不同的測試數(shù)據(jù)測試你的代碼功能毙死,來確保你沒有搞砸某些事情 —— 這會(huì)浪費(fèi)很多時(shí)間燎潮。
That’s especially true when automated tests could do this for you in seconds. If something’s gone wrong, tests will also assist in identifying the code that’s causing the unexpected behavior.
自動(dòng)化測試只需要數(shù)秒就可以完成以上的任務(wù)。 如果出現(xiàn)了錯(cuò)誤扼倘,測試程序還能夠幫助找出引發(fā)這個(gè)異常行為的代碼确封。
Sometimes it may seem a chore to tear yourself away from your productive, creative programming work to face the unglamorous and unexciting business of writing tests, particularly when you know your code is working properly.
有時(shí)候你可能會(huì)覺得這單調(diào)乏味、無趣的編寫測試程序簡直像家務(wù)活再菊,讓你脫離了有價(jià)值的爪喘、創(chuàng)造性的編程工作,尤其是當(dāng)你知道你的代碼能工作正常纠拔。
However, the task of writing tests is a lot more fulfilling than spending hours testing your application manually or trying to identify the cause of a newly-introduced problem.
而秉剑,比起用幾個(gè)小時(shí)的時(shí)間來手動(dòng)測試你的程序,或者試圖找出代碼中一個(gè)新引入的問題的原因稠诲,編寫測試程序還是令人愜意的侦鹏。
Tests don’t just identify problems, they prevent them?
It’s a mistake to think of tests merely as a negative aspect of development.
將測試工作僅僅當(dāng)成開發(fā)過程中的負(fù)擔(dān),是錯(cuò)誤的
Without tests, the purpose or intended behavior of an application might be rather opaque. Even when it’s your own code, you will sometimes find yourself poking around in it trying to find out what exactly it’s doing.
沒有測試臀叙,應(yīng)用程序的目的和行為將會(huì)變得相當(dāng)模糊略水。 甚至即使是你自己的代碼,有時(shí)候你也需要費(fèi)很大力氣去搞懂它到底干了些什么劝萤。
Tests change that; they light up your code from the inside, and when something goes wrong, they focus light on the part that has gone wrong - even if you hadn’t even realized it had gone wrong.
測試改變了這一切渊涝; 它們使你的代碼內(nèi)部變得明晰,當(dāng)錯(cuò)誤出現(xiàn)后稳其,它們會(huì)明確地指出哪部分代碼出了問題 —— 甚至你自己都不會(huì)料到問題會(huì)出現(xiàn)在那里驶赏。
Tests make your code more attractive?
讓你的代碼更有魅力
You might have created a brilliant piece of software, but you will find that many other developers will simply refuse to look at it because it lacks tests; without tests, they won’t trust it. Jacob Kaplan-Moss, one of Django’s original developers, says “Code without tests is broken by design.”
你可能開發(fā)過堪稱輝煌的軟件,但是你會(huì)發(fā)現(xiàn)許多其他的開發(fā)者會(huì)由于它缺少測試程序而拒絕查看它一眼既鞠;沒有測試程序煤傍,他們不會(huì)信任它。 Jacob Kaplan-Moss嘱蛋,Django最初的幾個(gè)開發(fā)者之一蚯姆,說過“不具有測試程序的代碼是設(shè)計(jì)上的錯(cuò)誤∪髅簦”
That other developers want to see tests in your software before they take it seriously is yet another reason for you to start writing tests.
你需要開始編寫測試的另一個(gè)原因就是其他的開發(fā)者在他們深入使用你的程序前可能想要查看一下它有沒有測試龄恋。
Tests help teams work together?
測試有助于團(tuán)隊(duì)合作
The previous points are written from the point of view of a single developer maintaining an application. Complex applications will be maintained by teams. Tests guarantee that colleagues don’t inadvertently break your code (and that you don’t break theirs without knowing). If you want to make a living as a Django programmer, you must be good at writing tests!
之前的觀點(diǎn)是從單個(gè)開發(fā)人員來維護(hù)一個(gè)程序這個(gè)方向來闡述的。 復(fù)雜的應(yīng)用將會(huì)被一個(gè)團(tuán)隊(duì)來維護(hù)凶伙。 測試能夠減少同事在無意間破壞你的代碼的機(jī)會(huì)(和你在不知情的情況下破壞別人的代碼的機(jī)會(huì))郭毕。 如果你想在團(tuán)隊(duì)中做一個(gè)好的Django開發(fā)者,你必須擅長測試函荣!
Basic testing strategies?
There are many ways to approach writing tests.
編寫測試有很多種方法显押。
Some programmers follow a discipline called “test-driven development”; they actually write their tests before they write their code. This might seem counter-intuitive, but in fact it’s similar to what most people will often do anyway: they describe a problem, then create some code to solve it. Test-driven development simply formalizes the problem in a Python test case.
一些開發(fā)者遵循一種叫做“test-driven development”的規(guī)則扳肛;
他們?cè)诰帉懘a前會(huì)先編好測試。
這似乎與直覺不符乘碑,盡管這種方法與大多數(shù)人經(jīng)常的做法很相似:人們先描述一個(gè)問題挖息,然后創(chuàng)建一些代碼來解決這個(gè)問題。
測試驅(qū)動(dòng)開發(fā)可以用Python測試用例將這個(gè)問題簡單地形式化兽肤。
More often, a newcomer to testing will create some code and later decide that it should have some tests. Perhaps it would have been better to write some tests earlier, but it’s never too late to get started.
更常見的情況是套腹,剛接觸測試的人會(huì)先編寫一些代碼,然后才決定為這些代碼創(chuàng)建一些測試资铡。也許在之前就編寫一些測試會(huì)好一點(diǎn)电禀,但什么時(shí)候開始都不算晚。
Sometimes it’s difficult to figure out where to get started with writing tests. If you have written several thousand lines of Python, choosing something to test might not be easy. In such a case, it’s fruitful to write your first test the next time you make a change, either when you add a new feature or fix a bug.
有時(shí)候很難解決從什么地方開始編寫測試害驹。
如果你已經(jīng)編寫了數(shù)千行Python代碼鞭呕,挑出一些功能去測試是很困難的。
這種情況下宛官,在下次你對(duì)代碼進(jìn)行變更葫松,或者添加一個(gè)新功能或者修復(fù)一個(gè)bug時(shí),編寫你的第一個(gè)測試底洗,效果會(huì)非常好腋么。
So let’s do that right away.
現(xiàn)在,讓我們馬上來編寫一個(gè)測試亥揖。
Writing our first test?
編寫我們的第一個(gè)測試
We identify a bug?
我們找出一個(gè)錯(cuò)誤
Fortunately, there’s a little bug in the polls application for us to fix right away: the Question.was_published_recently() method returns True if the Question was published within the last day (which is correct) but also if the Question’s pub_date
field is in the future (which certainly isn’t).
幸運(yùn)的是珊擂,polls應(yīng)用中有一個(gè)小錯(cuò)誤讓我們可以馬上來修復(fù)它:如果Question在最近一天發(fā)布,Question.was_published_recently() 方法返回True(這是對(duì)的)费变,但是如果Question的pub_date 字段是在未來摧扇,它還返回True(這肯定是不對(duì)的)。
To check if the bug really exists, using the Admin create a question whose date lies in the future and check the method using the shell
:
你可以在管理站點(diǎn)中看到這一點(diǎn)挚歧; 創(chuàng)建一個(gè)發(fā)布時(shí)間在未來的一個(gè)Question扛稽; 你可以看到Question 的變更列表聲稱它是最近發(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
Since things in the future are not ‘recent’, this is clearly wrong.
將來的事情并不能稱之為‘最近’滑负,這確實(shí)是一個(gè)錯(cuò)誤在张。
Create a test to expose the bug?
創(chuàng)建一個(gè)測試來暴露這個(gè)bug
What we’ve just done in the shell to test for the problem is exactly what we can do in an automated test, so let’s turn that into an automated test.
我們需要在自動(dòng)化測試?yán)镒龅暮蛣偛旁?shell 里做的差不多,讓我們來將它轉(zhuǎn)換成一個(gè)自動(dòng)化測試矮慕。
A conventional place for an application’s tests is in the application’s tests.py file; the testing system will automatically find tests in any file whose name begins with test.
應(yīng)用的測試用例安裝慣例一般放在該應(yīng)用的tests.py文件中帮匾;測試系統(tǒng)將自動(dòng)在任何以test開頭的文件中查找測試用例。
Put the following in the tests.py file in the polls application:
將下面的代碼放入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)
What we have done here is created a django.test.TestCase subclass with a method that creates a Question
instance with a pub_date in the future. We then check the output of was_published_recently() - which ought to be False.
我們?cè)谶@里做的是創(chuàng)建一個(gè)django.test.TestCase 子類痴鳄,它具有一個(gè)方法可以創(chuàng)建一個(gè)pub_date在未來的Question實(shí)例瘟斜。然后我們檢查was_published_recently()的輸出 —— 它應(yīng)該是 False.
Running tests?
運(yùn)行測試?
In the terminal, we can run our test:
在終端上,運(yùn)行我們的測試:
python manage.py test polls
and you’ll see something like:
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.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'...
What happened is this:
python manage.py test polls looked for tests in he polls application
it found a subclass of the [django.test.TestCase](https://docs.djangoproject.com/en/1.10/topics/testing/+ tools/#django.test.TestCase) class
it created a special database for the purpose of testing
it looked for test methods - ones whose names begin with test
in test_was_published_recently_with_future_question
it created a Question instance whose pub_date field is 30 days in the future
... and using the assertIs() method, it discovered that its was_published_recently() returns True, though we wanted it to return False
The test informs us which test failed and even the line on which the failure occurred.
發(fā)生了如下事情:
python manage.py test polls 查找polls下的測試用例
它找到 [django.test.TestCase](https://docs.djangoproject.com/en/1.10/topics/testing/+ tools/#django.test.TestCase) 子類
它為測試創(chuàng)建了一個(gè)特定的數(shù)據(jù)庫
它查找用于測試的方法 —— 名字以test開始
它運(yùn)行test_was_published_recently_with_future_question創(chuàng)建一個(gè)pub_date為未來30天的 Question實(shí)例
... 然后利用assertEqual()方法,它發(fā)現(xiàn)was_published_recently() 返回True哼转,盡管我們希望它返回False
這個(gè)測試通知我們哪個(gè)測試失敗明未,甚至是錯(cuò)誤出現(xiàn)在哪一行槽华。
Fixing the bug?
修復(fù)這個(gè)bug?
We already know what the problem is: Question.was_published_recently() should return False
if its pub_date is in the future. Amend the method in models.py, so that it will only return True if the date is also in the past:
我們已經(jīng)知道問題是什么:Question.was_published_recently() 應(yīng)該返回 False壹蔓,如果它的pub_date是在未來。在models.py中修復(fù)這個(gè)方法猫态,讓它只有當(dāng)日期是在過去時(shí)才返回True :
polls/models.py
def was_published_recently(self):
now = timezone.now()
return now - datetime.timedelta(days=1) <= self.pub_date <= now
and run the test again:
Creating test database for alias 'default'...
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
Destroying test database for alias 'default'...
After identifying a bug, we wrote a test that exposes it and corrected the bug in the code so our test passes.
在找出一個(gè)錯(cuò)誤之后佣蓉,我們編寫一個(gè)測試來暴露這個(gè)錯(cuò)誤,然后在代碼中更正這個(gè)錯(cuò)誤讓我們的測試通過亲雪。
Many other things might go wrong with our application in the future, but we can be sure that we won’t inadvertently reintroduce this bug, because simply running the test will warn us immediately. We can consider this little portion of the application pinned down safely forever.
未來勇凭,我們的應(yīng)用可能會(huì)出許多其它的錯(cuò)誤,但是我們可以保證我們不會(huì)無意中再次引入這個(gè)錯(cuò)誤义辕,因?yàn)楹唵蔚剡\(yùn)行一下這個(gè)測試就會(huì)立即提醒我們虾标。我們可以認(rèn)為這個(gè)應(yīng)用的這一小部分會(huì)永遠(yuǎn)安全了。
More comprehensive tests?
While we’re here, we can further pin down the was_published_recently() method; in fact, it would be positively embarrassing if in fixing one bug we had introduced another.
在這里灌砖,我們可以使was_published_recently() 方法更加穩(wěn)定璧函;事實(shí)上,在修復(fù)一個(gè)錯(cuò)誤的時(shí)候引入一個(gè)新的錯(cuò)誤將是一件很令人尷尬的事基显。
Add two more test methods to the same class, to test the behavior of the method more comprehensively:
在同一個(gè)類中添加兩個(gè)其它的測試方法蘸吓,來更加綜合地測試這個(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)
And now we have three tests that confirm that Question.was_published_recently() returns sensible values for past, recent, and future questions.
現(xiàn)在我們有三個(gè)測試來保證無論發(fā)布時(shí)間是在過去、現(xiàn)在還是未來 Question.was_published_recently()都將返回合理的數(shù)據(jù)撩幽。
Again, polls is a simple application, but however complex it grows in the future and whatever other code it interacts with, we now have some guarantee that the method we have written tests for will behave in expected ways.
再說一次库继,polls 應(yīng)用雖然簡單,但是無論它今后會(huì)變得多么復(fù)雜以及會(huì)和多少其它的應(yīng)用產(chǎn)生相互作用窜醉,我們都能保證我們剛剛為它編寫過測試的那個(gè)方法會(huì)按照預(yù)期的那樣工作宪萄。
Test a view?
The polls application is fairly undiscriminating: it will publish any question, including ones whose pub_date field lies in the future. We should improve this. Setting a pub_date
in the future should mean that the Question is published at that moment, but invisible until then.
這個(gè)投票應(yīng)用沒有區(qū)分能力:它將會(huì)發(fā)布任何一個(gè)Question,包括 pub_date字段位于未來榨惰。我們應(yīng)該改進(jìn)這一點(diǎn)拜英。 設(shè)定pub_date在未來應(yīng)該表示Question在未來發(fā)布,但是直到那個(gè)時(shí)間點(diǎn)才會(huì)變得可見读串。
A test for a view?
測試視圖?
When we fixed the bug above, we wrote the test first and then the code to fix it. In fact that was a simple example of test-driven development, but it doesn’t really matter in which order we do the work.
當(dāng)我們修復(fù)上面的錯(cuò)誤時(shí)聊记,我們先寫測試,然后修改代碼來修復(fù)它恢暖。 事實(shí)上排监,這是測試驅(qū)動(dòng)開發(fā)的一個(gè)簡單的例子,但做的順序并不真的重要杰捂。
In our first test, we focused closely on the internal behavior of the code. For this test, we want to check its behavior as it would be experienced by a user through a web browser.
在我們的第一個(gè)測試中舆床,我們專注于代碼內(nèi)部的行為。 在這個(gè)測試中,我們想要通過瀏覽器從用戶的角度來檢查它的行為挨队。
Before we try to fix anything, let’s have a look at the tools at our disposal.
在我們?cè)囍迯?fù)任何事情之前谷暮,讓我們先查看一下我們能用到的工具。
The Django test client?
Django provides a test Client to simulate a user interacting with the code at the view level. We can use it in tests.py or even in the shell.
Django提供了一個(gè)測試客戶端Client來模擬用戶和視圖函數(shù)代碼的交互盛垦,我們可以在tests.py中使用湿弦,甚至在shell中使用。
We will start again with the shell, where we need to do a couple of things that won’t be necessary in tests.py. The first is to set up the test environment in the shell:
我們?cè)俅螁?dòng)shell腾夯,但是在shell中我們需要做很多在tests.py中不必做的事颊埃。首先是在shell中設(shè)置測試環(huán)境:
>>> from django.test.utils import setup_test_environment
>>> setup_test_environment()
setup_test_environment() installs a template renderer which will allow us to examine some additional attributes on responses such as response.context that otherwise wouldn’t be available. Note that this method does not setup a test database, so the following will be run against the existing database and the output may differ slightly depending on what questions you already created. You might get unexpected results if your TIME_ZONE in settings.py isn’t correct. If you don’t remember setting it earlier, check it before continuing.
setup_test_environment()安裝一個(gè)模板渲染器,可以使我們來檢查響應(yīng)的一些額外屬性比如response.context蝶俱,否則是訪問不到的班利。請(qǐng)注意,這種方法不會(huì)建立一個(gè)測試數(shù)據(jù)庫榨呆,所以以下命令將運(yùn)行在現(xiàn)有的數(shù)據(jù)庫上罗标,輸出的內(nèi)容也會(huì)根據(jù)你已經(jīng)創(chuàng)建的Question不同而稍有不同。如果你的TIME_ZONE 設(shè)置不正確积蜻,得出的結(jié)果可能會(huì)跟預(yù)期不一致闯割。如果之前你忘記設(shè)置了,測試開始前請(qǐng)先確認(rèn)一下浅侨。
Next we need to import the test client class (later in tests.py
we will use the django.test.TestCase class, which comes with its own client, so this won’t be required):
下一步我們需要導(dǎo)入測試客戶端類(在之后的tests.py
中纽谒,我們將使用 django.test.TestCase 類,它具有自己的客戶端如输,將不需要導(dǎo)入這個(gè)類):
>>> from django.test import Client
>>> # create an instance of the client for our use
>>> client = Client()
With that ready, we can ask the client to do some work for us:
>>> # 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.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's up?</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']
<QuerySet [<Question: What's up?>]>
Improving our view?
The list of polls shows polls that aren’t published yet (i.e. those that have a pub_date in the future). Let’s fix that.In Tutorial 4 we introduced a class-based view, based on ListView:
投票的列表中包含了那些還沒有發(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]
We need to amend the get_queryset() method and change it so that it also checks the date by comparing it with timezone.now(). First we need to add an import:
我們需要修改get_queryset方法并讓它將日期與timezone.now()進(jìn)行比較不见。首先我們需要添加一行導(dǎo)入:
polls/views.py
from django.utils import timezone
and then we must amend the get_queryset method like so:
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()) returns a queryset containing Questions whose pub_date is less than or equal to - that is, earlier than or equal to - timezone.now.
Question.objects.filter(pub_date__lte=timezone.now()) 返回一個(gè)查詢集澳化,包含pub_date小于等于timezone.now的Question。
Testing our new view?
測試我們的視圖?
Now you can satisfy yourself that this behaves as expected by firing up the runserver, loading the site in your browser, creating Questions with dates in the past and future, and checking that only those that have been published are listed. You don’t want to have to do that every single time you make any change that might affect this - so let’s also create a test, based on our shell session above.
啟動(dòng)服務(wù)器稳吮、在瀏覽器中載入站點(diǎn)缎谷、創(chuàng)建一些發(fā)布時(shí)間在過去和將來的Questions,然后檢驗(yàn)只有已經(jīng)發(fā)布的Question會(huì)展示出來灶似,至此列林,你應(yīng)該感到滿意了。你不想每次修改可能與這相關(guān)的代碼時(shí)都重復(fù)這樣做 —— 所以讓我們基于以上shell會(huì)話中的內(nèi)容酪惭,再編寫一個(gè)測試希痴。
Add the following to polls/tests.py:
將下面的代碼添加到polls/tests.py:
polls/tests.py
from django.urls import reverse
and we’ll create a shortcut function to create questions as well as a new test class:
polls/tests.py
def create_question(question_text, days):
"""
Creates 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)
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.")
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.>']
)
Let’s look at some of these more closely.
我們?cè)敿?xì)解釋一下
First is a question shortcut function, create_question, to take some repetition out of the process of creating questions.
首先是question的快捷函數(shù)create_question,封裝了創(chuàng)建Question的重復(fù)步驟
test_index_view_with_no_questions doesn’t create any questions, but checks the message: “No polls are available.” and verifies the latest_question_list is empty. Note that the django.test.TestCase class provides some additional assertion methods. In these examples, we use assertContains() andassertQuerysetEqual().
test_index_view_with_no_questions 并不會(huì)創(chuàng)建人和的Questions春感,但是會(huì)檢查“No polls are available.” 信息并且驗(yàn)證latest_question_list 是空砌创。注意 django.test.TestCase類還封裝了其他的assertion方法虏缸,在這里,我們用了 assertContains() andassertQuerysetEqual().
In test_index_view_with_a_past_question, we create a question and verify that it appears in the list.
test_index_view_with_a_past_question函數(shù)中嫩实,我們創(chuàng)建了一個(gè)Question然后驗(yàn)證它是否在列表中
In test_index_view_with_a_future_question, we create a question with a pub_date in the future. The database is reset for each test method, so the first question is no longer there, and so again the index shouldn’t have any questions in it.
在test_index_view_with_a_future_question中刽辙,我們創(chuàng)建一個(gè)pub_date 在未來的Question。數(shù)據(jù)庫會(huì)為每一個(gè)測試方法進(jìn)行重置甲献,所以第一個(gè)Question已經(jīng)不在那里宰缤,因此首頁面里不應(yīng)該有任何Question。
And so on. In effect, we are using the tests to tell a story of admin input and user experience on the site, and checking that at every state and for every new change in the state of the system, the expected results are published.
等等竟纳。 事實(shí)上撵溃,我們是在用測試模擬站點(diǎn)上的管理員輸入和用戶體驗(yàn),檢查針對(duì)系統(tǒng)每一個(gè)狀態(tài)和狀態(tài)的新變化锥累,發(fā)布的是預(yù)期的結(jié)果。
Testing the DetailView?
測試DetailView?
What we have works well; however, even though future questions don’t appear in the index, users can still reach them if they know or guess the right URL. So we need to add a similar constraint to DetailView:
現(xiàn)在一切都還順利集歇;但是即使未來發(fā)布的Question不會(huì)出現(xiàn)在index中桶略,如果用戶知道或者猜出正確的URL依然可以訪問它們。所以我們需要給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())
And of course, we will add some tests, to check that a Question whose pub_date is in the past can be displayed, and that one with a pub_date in the future is not:
當(dāng)然诲宇,我們將增加一些測試來檢驗(yàn)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)
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):
"""
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)
url = reverse('polls:detail', args=(past_question.id,))
response = self.client.get(url)
self.assertContains(response, past_question.question_text)
Ideas for more tests?
We ought to add a similar get_queryset method to ResultsView and create a new test class for that view. It’ll be very similar to what we have just created; in fact there will be a lot of repetition.
我們應(yīng)該添加一個(gè)類似get_queryset的方法到ResultsView并為該視圖創(chuàng)建一個(gè)新的類。這將與我們剛剛創(chuàng)建的非常類似姑蓝;實(shí)際上將會(huì)有許多重復(fù)鹅心。
We could also improve our application in other ways, adding tests along the way. For example, it’s silly that Questions can be published on the site that have no Choices. So, our views could check for this, and exclude such Questions. Our tests would reate a Question without Choices and then test that it’s not published, as well as create a similar Question with Choices, and test that it is published.
我們還可以在其它方面改進(jìn)我們的應(yīng)用,并隨之不斷增加測試纺荧。例如旭愧,發(fā)布一個(gè)沒有Choices的Questions就顯得很愚蠢。所以宙暇,我們的視圖應(yīng)該檢查這點(diǎn)并排除這些 Questions输枯。我們的測試應(yīng)該創(chuàng)建一個(gè)不帶Choices 的 Question然后測試它不會(huì)發(fā)布出來, 同時(shí)創(chuàng)建一個(gè)類似的帶有 Choices的Question 并驗(yàn)證它會(huì) 發(fā)布出來占贫。
Perhaps logged-in admin users should be allowed to see unpublished Questions, but not ordinary visitors. Again: whatever needs to be added to the software to accomplish this should be accompanied by a test, whether you write the test first and then make the code pass the test, or work out the logic in your code first and then write a test to prove it.
也許登陸的用戶應(yīng)該被允許查看還沒發(fā)布的 Questions桃熄,但普通游客不行。 再說一次:無論添加什么代碼來完成這個(gè)要求型奥,需要提供相應(yīng)的測試代碼瞳收,無論你是否是先編寫測試然后讓這些代碼通過測試,還是先用代碼解決其中的邏輯然后編寫測試來證明它厢汹。
At a certain point you are bound to look at your tests and wonder whether your code is suffering from test bloat, which brings us to:
從某種程度上來說螟深,你一定會(huì)查看你的測試,然后想知道是否你的測試程序過于臃腫坑匠,這將我們帶向下面的內(nèi)容:
When testing, more is better?
It might seem that our tests are growing out of control. At this rate there will soon be more code in our tests than in our application, and the repetition is unaesthetic, compared to the elegant conciseness of the rest of our code.
看起來我們的測試代碼的增長正在失去控制血崭。 以這樣的速度,測試的代碼量將很快超過我們的應(yīng)用,對(duì)比我們其它優(yōu)美簡潔的代碼夹纫,重復(fù)毫無美感咽瓷。
It doesn’t matter. Let them grow. For the most part, you can write a test once and then forget about it. It will continue performing its useful function as you continue to develop your program.
沒關(guān)系。讓它們繼續(xù)增長舰讹。最重要的是茅姜,你可以寫一個(gè)測試一次,然后忘了它月匣。 當(dāng)你繼續(xù)開發(fā)你的程序時(shí)钻洒,它將繼續(xù)執(zhí)行有用的功能。
Sometimes tests will need to be updated. Suppose that we amend our views so that only Questions with Choices are published. In that case, many of our existing tests will fail - telling us exactly which tests need to be amended to bring them up to date, so to that extent tests help look after themselves.
有時(shí)锄开,測試需要更新素标。 假設(shè)我們修改我們的視圖使得只有具有Choices的 Questions 才會(huì)發(fā)布。在這種情況下萍悴,我們?cè)S多已經(jīng)存在的測試都將失敗 —— 這會(huì)告訴我們哪些測試需要被修改來使得它們保持最新头遭,所以從某種程度上講,測試可以自己照顧自己癣诱。
At worst, as you continue developing, you might find that you have some tests that are now redundant. Even that’s not a problem; in testing redundancy is a good thing.
在最壞的情況下计维,在你的開發(fā)過程中,你會(huì)發(fā)現(xiàn)許多測試現(xiàn)在變得冗余撕予。 即使這樣鲫惶,也不是問題;對(duì)測試來說实抡,冗余是一件好 事欠母。
As long as your tests are sensibly arranged, they won’t become unmanageable. Good rules-of-thumb include having:
只要你的測試被合理地組織,它們就不會(huì)變得難以管理澜术。 從經(jīng)驗(yàn)上來說艺蝴,好的做法是:
a separate TestClass for each model or view
每個(gè)模型或試圖具有一個(gè)單獨(dú)的TestClass
a separate test method for each set of conditions you want to test
為你想測試的各種情況創(chuàng)建一個(gè)單獨(dú)的測試方法
test method names that describe their function
測試方法的名字可以描述它的功能
Further testing?
This tutorial only introduces some of the basics of testing. There’s a great deal more you can do, and a number of very useful tools at your disposal to achieve some very clever things.
本教程只介紹了一些基本的測試。 還有很多你可以做鸟废,有許多非常有用的工具可以隨便使用來你實(shí)現(xiàn)一些非常有用的事情猜敢。
For example, while our tests here have covered some of the internal logic of a model and the way our views publish information, you can use an “in-browser” framework such as Selenium to test the way your HTML actually renders in a browser. These tools allow you to check not just the behavior of your Django code, but also, for example, of your JavaScript. It’s quite something to see the tests launch a browser, and start interacting with your site, as if a human being were driving it! Django includes LiveServerTestCase to facilitate integration with tools like Selenium.
例如,雖然我們的測試覆蓋了模型的內(nèi)部邏輯和視圖發(fā)布信息的方式盒延,但你還可以使用一個(gè)“瀏覽器”框架例如Selenium來測試你的HTML文件在瀏覽器中真實(shí)渲染的樣子缩擂。 這些工具不僅可以讓你檢查你的Django代碼的行為,還能夠檢查你的JavaScript的行為添寺。它會(huì)啟動(dòng)一個(gè)瀏覽器胯盯,并開始與你的網(wǎng)站進(jìn)行交互,就像有一個(gè)人在操縱一樣计露,非常值得一看博脑! Django 包含一個(gè) LiveServerTestCase來幫助與Selenium 這樣的工具集成憎乙。
If you have a complex application, you may want to run tests automatically with every commit for the purposes of continuous integration, so that quality control is itself - at least partially - automated. A good way to spot untested parts of your application is to check code coverage. This also helps identify fragile or even dead code. If you can’t test a piece of code, it usually means that code should be refactored or removed. Coverage will help to identify dead code. See Integration with coverage.py for details.
發(fā)現(xiàn)你應(yīng)用中未經(jīng)測試的代碼的一個(gè)好方法是檢查測試代碼的覆蓋率。 這也有助于識(shí)別脆弱的甚至死代碼叉趣。如果你不能測試一段代碼泞边,這通常意味著這些代碼需要被重構(gòu)或者移除。Coverage將幫助我們識(shí)別死代碼疗杉。 查看與coverage.py 集成來了解更多細(xì)節(jié)阵谚。
Testing in Django has comprehensive information about testing.
Testing in Django有更多關(guān)于測試的信息
What’s next??
接下來怎么辦??
For full details on testing, see Testing in Django.
所有關(guān)于測試的細(xì)節(jié),請(qǐng)看Testing in Django.
When you’re comfortable with testing Django views, read part 6 of this tutorial to learn about static files management.
如果你你對(duì)測試Django視圖的已經(jīng)熟悉烟具,請(qǐng)看教程6來學(xué)習(xí)靜態(tài)文件的處理