摘要:修改某一個(gè)組件可能會(huì)導(dǎo)致另一個(gè)組件出現(xiàn)意想不到的,但是在人工測(cè)試時(shí)卻很難檢查出來(lái),總不能每寫幾行代碼就把整個(gè)網(wǎng)站統(tǒng)統(tǒng)檢查一遍吧。比如說(shuō)有個(gè)功能,限制每個(gè)用戶每天發(fā)表評(píng)論不能超過(guò)條,人工測(cè)試就顯得比較麻煩,特別是需要反復(fù)調(diào)試的時(shí)候。
測(cè)試是伴隨著開發(fā)進(jìn)行的,開發(fā)有多久,測(cè)試就要多久。本教程已經(jīng)進(jìn)行了30多章了,都是如何測(cè)試的?當(dāng)然是runserver啦!每當(dāng)開發(fā)新功能后,都需要運(yùn)行服務(wù)器,假裝自己就是用戶,測(cè)試是否運(yùn)行正常。
這樣的人工測(cè)試優(yōu)點(diǎn)是非常直觀,你看到的和用戶看到的是完全相同的。但是缺點(diǎn)也很明顯:
效率低。在開發(fā)時(shí)可能你需要反復(fù)的修改代碼、測(cè)試功能,這樣重復(fù)查看幾十次甚至幾百次網(wǎng)頁(yè)時(shí)會(huì)相當(dāng)?shù)淖屓藷┰辍?/p>
容易遺漏bug。隨著你的項(xiàng)目越來(lái)越復(fù)雜,組件之間的交互也更加復(fù)雜。修改某一個(gè)組件可能會(huì)導(dǎo)致另一個(gè)組件出現(xiàn)意想不到的bug,但是在人工測(cè)試時(shí)卻很難檢查出來(lái),總不能每寫幾行代碼就把整個(gè)網(wǎng)站統(tǒng)統(tǒng)檢查一遍吧。過(guò)了很久之后你終于發(fā)現(xiàn)了這個(gè)bug,但此時(shí)你已經(jīng)搞不清它來(lái)源于什么地方了。
有的測(cè)試不方便進(jìn)行。比如說(shuō)有個(gè)功能,限制每個(gè)用戶每天發(fā)表評(píng)論不能超過(guò)10條,人工測(cè)試就顯得比較麻煩,特別是需要反復(fù)調(diào)試的時(shí)候。
為了解決人工測(cè)試的種種問(wèn)題,Django引入了Python標(biāo)準(zhǔn)庫(kù)的單元測(cè)試模塊,也就是自動(dòng)化測(cè)試了:你可以寫一段代碼,讓代碼幫你測(cè)試?。ǔ绦騿T是最會(huì)偷懶的職業(yè)..)代碼會(huì)忠實(shí)的完成測(cè)試任務(wù),幫助你從繁重的測(cè)試工作中解脫出來(lái)。除此之外,自動(dòng)化測(cè)試還有以下優(yōu)點(diǎn):
預(yù)防錯(cuò)誤。當(dāng)應(yīng)用過(guò)于復(fù)雜時(shí),代碼的意圖會(huì)變得非常不清晰,甚至你都看不懂自己寫的代碼,這是很常見的。而測(cè)試就好像是從內(nèi)部審查代碼一樣,可以幫助你發(fā)現(xiàn)微小的錯(cuò)誤。
有利于團(tuán)隊(duì)協(xié)作。良好的測(cè)試保證其他人不會(huì)不小心破壞了你的代碼(也保證你不會(huì)不小心弄壞別人的..)?,F(xiàn)在已經(jīng)不是單打獨(dú)斗出英雄的年代了,想要成為優(yōu)秀的Django程序員,你必須擅長(zhǎng)編寫測(cè)試!
雖然學(xué)習(xí)自動(dòng)化測(cè)試不會(huì)讓你的博客增加一絲絲的功能,但是可以讓代碼更加強(qiáng)壯,所以我覺得很有必要拿出一章來(lái)專門講講。
Django官方文檔的第5部分講測(cè)試講得非常的好,并且有中文版本。本章節(jié)就大量借鑒了官方文檔,也非常非常推薦讀者去拜讀。第一個(gè)測(cè)試 給我bug!
為了演示測(cè)試是如何工作的,讓我們首先在文章模型中寫個(gè)有bug的方法:
article/models.py from django.utils import timezone class ArticlePost(models.Model): ... def was_created_recently(self): # 若文章是"最近"發(fā)表的,則返回 True diff = timezone.now() - self.created if diff.days <= 0 and diff.seconds < 60: return True else: return False
這個(gè)方法用于檢測(cè)當(dāng)前文章是否是最近發(fā)表的。
這個(gè)方法稍微擴(kuò)展一下就會(huì)變得非常實(shí)用。比如可以將博文的發(fā)表日期顯示為“剛剛”、“3分鐘前”、“5小時(shí)前”等相對(duì)時(shí)間,用戶體驗(yàn)將大有提升。
仔細(xì)看看,它是沒辦法正確判斷“未來(lái)”的文章的:
>>> import datetime >>> from django.utils import timezone >>> from article.models import ArticlePost >>> from django.contrib.auth.models import User # 創(chuàng)建一篇"未來(lái)"的文章 >>> future_article = ArticlePost(author=User(username="user"), title="test",body="test", created=timezone.now() + datetime.timedelta(days=30)) # 是否是“最近”發(fā)表的? >>> future_article.was_created_recently() True
未來(lái)發(fā)生的肯定不是最近發(fā)生的,因此代碼是錯(cuò)誤的。
寫個(gè)測(cè)試暴露bug接下來(lái)就要寫測(cè)試用例,將測(cè)試轉(zhuǎn)為自動(dòng)化。
還記得最初生成文章app時(shí)候的目錄結(jié)構(gòu)嗎?
article │ admin.py │ apps.py │ models.py │ tests.py │ views.py │ __init__.py │ └─migrations └─ __init__.py
這個(gè)tests.py就是留給你寫測(cè)試用例的地方了:
article/tests.py from django.test import TestCase import datetime from django.utils import timezone from article.models import ArticlePost from django.contrib.auth.models import User class ArticlePostModelTests(TestCase): def test_was_created_recently_with_future_article(self): # 若文章創(chuàng)建時(shí)間為未來(lái),返回 False author = User(username="user", password="test_password") author.save() future_article = ArticlePost( author=author, title="test", body="test", created=timezone.now() + datetime.timedelta(days=30) ) self.assertIs(future_article.was_created_recently(), False)
基本就是把剛才在Shell中的測(cè)試代碼抄了過(guò)來(lái)。有點(diǎn)不同的是末尾這個(gè)assertIs方法,了解“斷言”的同學(xué)會(huì)對(duì)它很熟悉:它的作用是檢測(cè)方法內(nèi)的兩個(gè)參數(shù)是否完全一致,如果不是則拋出異常,提醒你這個(gè)地方是有問(wèn)題滴。
接下來(lái)運(yùn)行測(cè)試:
(env) > python manage.py test
運(yùn)行結(jié)果如下:
Creating test database for alias "default"... System check identified no issues (0 silenced). F ====================================================================== FAIL: test_was_created_recently_with_future_article (article.tests.ArticlePostModelTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "E:django_projectmy_blogarticle ests.py", line 19, in test_was_created_recently_with_future_article self.assertIs(future_article.was_created_recently(), False) AssertionError: True is not False ---------------------------------------------------------------------- Ran 1 test in 0.000s FAILED (failures=1) Destroying test database for alias "default"...
這里面名堂就很多了:
首先測(cè)試系統(tǒng)會(huì)在所有以tests開頭的文件中尋找測(cè)試代碼
所有TestCase的子類都被認(rèn)為是測(cè)試代碼
系統(tǒng)創(chuàng)建了一個(gè)特殊的數(shù)據(jù)庫(kù)供測(cè)試使用,即所有測(cè)試產(chǎn)生的數(shù)據(jù)不會(huì)對(duì)你自己的數(shù)據(jù)庫(kù)造成影響
類中所有以test開頭的方法會(huì)被認(rèn)為是測(cè)試用例
在運(yùn)行測(cè)試用例時(shí),assertIs拋出異常,因?yàn)?b>True is not False
完成測(cè)試后,自動(dòng)銷毀測(cè)試數(shù)據(jù)庫(kù)
測(cè)試系統(tǒng)明確指明了錯(cuò)誤的數(shù)量、位置和種類等信息,請(qǐng)讀者細(xì)細(xì)品嘗。
修正bug既然通過(guò)測(cè)試找到了bug,那接下來(lái)就要把代碼進(jìn)行修正:
article/models.py from django.utils import timezone class ArticlePost(models.Model): ... def was_created_recently(self): diff = timezone.now() - self.created # if diff.days <= 0 and diff.seconds < 60: if diff.days == 0 and diff.seconds >= 0 and diff.seconds < 60: return True else: return False
重新運(yùn)行測(cè)試:
(env) > python manage.py test Creating test database for alias "default"... System check identified no issues (0 silenced). . ---------------------------------------------------------------------- Ran 1 test in 0.000s OK Destroying test database for alias "default"...
這次代碼順利通過(guò)了測(cè)試。
可以肯定的是,在往后的開發(fā)中,這個(gè)bug不會(huì)再出現(xiàn)了,因?yàn)槟阒恍枰\(yùn)行一遍測(cè)試,就會(huì)立即得到警告。可以認(rèn)為項(xiàng)目的這一小部分代碼永遠(yuǎn)是安全的。
更全面的測(cè)試既然一個(gè)測(cè)試用例就可以保證一小段代碼永遠(yuǎn)安全,那我寫一堆測(cè)試豈不是可以保證整個(gè)項(xiàng)目永遠(yuǎn)安全嗎?確實(shí)如此,這個(gè)買賣絕對(duì)是不虧的。
因此我們繼續(xù)再增加幾個(gè)測(cè)試,全面強(qiáng)化代碼:
article/tests.py ... from django.test import TestCase import datetime from django.utils import timezone from article.models import ArticlePost from django.contrib.auth.models import User class ArticlePostModelTests(TestCase): def test_was_created_recently_with_future_article(self): # 若文章創(chuàng)建時(shí)間為未來(lái),返回 False ... def test_was_created_recently_with_seconds_before_article(self): # 若文章創(chuàng)建時(shí)間為 1 分鐘內(nèi),返回 True author = User(username="user1", password="test_password") author.save() seconds_before_article = ArticlePost( author=author, title="test1", body="test1", created=timezone.now() - datetime.timedelta(seconds=45) ) self.assertIs(seconds_before_article.was_created_recently(), True) def test_was_created_recently_with_hours_before_article(self): # 若文章創(chuàng)建時(shí)間為幾小時(shí)前,返回 False author = User(username="user2", password="test_password") author.save() hours_before_article = ArticlePost( author=author, title="test2", body="test2", created=timezone.now() - datetime.timedelta(hours=3) ) self.assertIs(hours_before_article.was_created_recently(), False) def test_was_created_recently_with_days_before_article(self): # 若文章創(chuàng)建時(shí)間為幾天前,返回 False author = User(username="user3", password="test_password") author.save() months_before_article = ArticlePost( author=author, title="test3", body="test3", created=timezone.now() - datetime.timedelta(days=5) ) self.assertIs(months_before_article.was_created_recently(), False)
現(xiàn)在我們擁有了4個(gè)測(cè)試,來(lái)保證was_created_recently()方法對(duì)于過(guò)去、最近、未來(lái)中的4種情況都返回正確的值。你還可以繼續(xù)擴(kuò)展,直到你覺得完全沒有任何bug藏匿的可能性為止。
在實(shí)際的開發(fā)中,有些難纏的bug會(huì)把自己偽裝得非常的好,而不是像教程這樣明確的知道它就在那里。有了自動(dòng)化測(cè)試,無(wú)論以后你的項(xiàng)目怎么變化、app交互多么的復(fù)雜,只要在測(cè)試中寫好的邏輯就一定是符合預(yù)期的,而你所需要做的只是運(yùn)行一條測(cè)試指令而已。
雖然教程中僅使用了assertIs,但實(shí)際上Django中的斷言有大概幾十種之多,比如assertEqual、assertContains等,并且還在不斷更新。詳見Python標(biāo)準(zhǔn)斷言和Django擴(kuò)展斷言測(cè)試視圖
上面的測(cè)試都是針對(duì)模型的。視圖該怎么測(cè)試?如何通過(guò)測(cè)試系統(tǒng)模擬出用戶的請(qǐng)求呢?
答案是TestCase類提供了一個(gè)供測(cè)試使用的Client來(lái)模擬用戶通過(guò)請(qǐng)求和視圖層代碼的交互。
以文章詳情視圖的瀏覽量統(tǒng)計(jì)為例,比較容易出現(xiàn)的潛在bug有:
增加的瀏覽量未能正常保存進(jìn)數(shù)據(jù)庫(kù)(即每次請(qǐng)求則瀏覽量+1)
增加瀏覽量的同時(shí),updated字段也錯(cuò)誤的一并更新
所以有針對(duì)的寫2條測(cè)試。新寫一個(gè)專門測(cè)試視圖的類,與前面的測(cè)試模型的類區(qū)分開:
article/tests.py ... from time import sleep from django.urls import reverse class ArticlePostModelTests(TestCase): ... class ArtitclePostViewTests(TestCase): def test_increase_views(self): # 請(qǐng)求詳情視圖時(shí),閱讀量 +1 author = User(username="user4", password="test_password") author.save() article = ArticlePost( author=author, title="test4", body="test4", ) article.save() self.assertIs(article.total_views, 0) url = reverse("article:article_detail", args=(article.id,)) response = self.client.get(url) viewed_article = ArticlePost.objects.get(id=article.id) self.assertIs(viewed_article.total_views, 1) def test_increase_views_but_not_change_updated_field(self): # 請(qǐng)求詳情視圖時(shí),不改變 updated 字段 author = User(username="user5", password="test_password") author.save() article = ArticlePost( author=author, title="test5", body="test5", ) article.save() sleep(0.5) url = reverse("article:article_detail", args=(article.id,)) response = self.client.get(url) viewed_article = ArticlePost.objects.get(id=article.id) self.assertIs(viewed_article.updated - viewed_article.created < timezone.timedelta(seconds=0.1), True)
注意看代碼是如何與視圖層交互的:response = self.client.get(url)向視圖發(fā)起請(qǐng)求并獲得了響應(yīng),剩下的就是從數(shù)據(jù)庫(kù)中取出更新后的數(shù)據(jù),并用斷言語(yǔ)句來(lái)判斷代碼是否符合預(yù)期了。
運(yùn)行測(cè)試:
(env) > python manage.py test Creating test database for alias "default"... System check identified no issues (0 silenced). ...... ---------------------------------------------------------------------- Ran 6 tests in 0.617s OK Destroying test database for alias "default"...
6條測(cè)試用例全部通過(guò)。
越多越好的測(cè)試僅僅是app中的兩個(gè)非常小的功能,就已經(jīng)寫了6條測(cè)試用例了,并且還可以繼續(xù)擴(kuò)展。除此之外,其他的每個(gè)模型、視圖都可以擴(kuò)展出幾十甚至上百條測(cè)試,這樣下去代碼總量很快就要失去控制了,并且相對(duì)于業(yè)務(wù)代碼來(lái)說(shuō),測(cè)試代碼顯得繁瑣且不夠優(yōu)雅。
但是沒關(guān)系!就讓測(cè)試代碼繼續(xù)肆意增長(zhǎng)吧。大部分情況下,你寫完一個(gè)測(cè)試之后就可以忘掉它了。在你繼續(xù)開發(fā)的過(guò)程中,它會(huì)一直默默無(wú)聞地為你做貢獻(xiàn)的。最壞的情況是當(dāng)你繼續(xù)開發(fā)的時(shí)候,發(fā)現(xiàn)之前的一些測(cè)試現(xiàn)在看來(lái)是多余的。但是這也不是什么問(wèn)題,多做些測(cè)試也不錯(cuò)。
深入代碼測(cè)試在前面的測(cè)試中,我們已經(jīng)從模型層和視圖層的角度檢查了應(yīng)用的輸入輸出,但是模板呢?雖然可以用assertInHTML、assertJSONEqual等斷言大致檢查模板中的某些內(nèi)容,但更加近似于瀏覽器的檢查就要使用Selenium等測(cè)試工具(畢竟Django的重點(diǎn)是后端而不是前端)。
Selenium不僅可以測(cè)試 Django 框架里的代碼,甚至還可以檢查 JavaScript代碼。它假裝成是一個(gè)正在和你站點(diǎn)進(jìn)行交互的瀏覽器,就好像有個(gè)真人在訪問(wèn)網(wǎng)站一樣。Django 提供了LiveServerTestCase來(lái)和Selenium這樣的工具進(jìn)行交互。
關(guān)于測(cè)試的話題這里只是開了個(gè)頭,讀者可以繼續(xù)閱讀下面的內(nèi)容進(jìn)一步了解:
Django: Writing and running tests
Django: Testing tools
Django: Advanced testing topics
Selenium官方文檔
總結(jié)有一幫崇尚“測(cè)試驅(qū)動(dòng)”的開發(fā)者,他們開發(fā)時(shí)先寫測(cè)試代碼,然后才寫業(yè)務(wù)代碼。而普通開發(fā)者通常是先寫業(yè)務(wù)代碼,再寫測(cè)試代碼,這也是沒問(wèn)題的。但如果你已經(jīng)寫了很多業(yè)務(wù)代碼了,再回頭寫測(cè)試確實(shí)有些無(wú)從下手,那么至少在以后寫新功能時(shí),記得加上測(cè)試。測(cè)試寫得好不好,甚至比功能本身更能看出編程水平。
測(cè)試可以讓代碼更加強(qiáng)壯。項(xiàng)目沒出bug時(shí),皆大歡喜,有沒有測(cè)試都一樣;一旦出現(xiàn)難纏的bug,你就會(huì)無(wú)比想念一套完善的測(cè)試代碼了。
博主寫自己的網(wǎng)站時(shí)就沒有對(duì)測(cè)試給與足夠的重視,回想起來(lái)走了很多彎路。希望讀者以前車之鑒,培養(yǎng)良好的編程習(xí)慣。
有疑問(wèn)請(qǐng)?jiān)诙刨惖膫€(gè)人網(wǎng)站留言,我會(huì)盡快回復(fù)。
或Email私信我:[email protected]
項(xiàng)目完整代碼:Django_blog_tutorial
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/45096.html
摘要:比如,在一個(gè)博客應(yīng)用中,你可能會(huì)創(chuàng)建如下幾個(gè)視圖博客首頁(yè)展示最近的幾項(xiàng)內(nèi)容。這些需求都靠視圖來(lái)完成。首先寫一個(gè)最簡(jiǎn)單的視圖函數(shù),在瀏覽器中打印出字符串。調(diào)用函數(shù)時(shí)會(huì)返回一個(gè)含字符串的對(duì)象。換句話說(shuō),的作用是將映射到視圖中。 Django 中的視圖的概念是「一類具有相同功能和模板的網(wǎng)頁(yè)的集合」。比如,在一個(gè)博客應(yīng)用中,你可能會(huì)創(chuàng)建如下幾個(gè)視圖: 博客首頁(yè):展示最近的幾項(xiàng)內(nèi)容。 內(nèi)容詳情...
摘要:教程看到這里,你已經(jīng)學(xué)會(huì)如下內(nèi)容搭建開發(fā)環(huán)境博文管理用戶管理發(fā)表評(píng)論若干小功能搭建簡(jiǎn)單的小博客,以上的功能夠用了。教程為了起步平緩,沒有展開這方面的內(nèi)容。陌生人,祝你學(xué)業(yè)進(jìn)步事業(yè)有成歡迎常到杜賽的個(gè)人網(wǎng)站做客 教程看到這里,你已經(jīng)學(xué)會(huì)如下內(nèi)容: 搭建開發(fā)環(huán)境 博文管理 用戶管理 發(fā)表評(píng)論 若干小功能 搭建簡(jiǎn)單的小博客,以上的功能夠用了。 相信你的志向不止于此。畢竟程序員面試個(gè)個(gè)造火...
摘要:既然有登錄登出,那么用戶的注冊(cè)肯定也是少不了的。用戶在注冊(cè)成功后會(huì)自動(dòng)登錄并返回博客列表頁(yè)面。總結(jié)本章用到了表單類對(duì)數(shù)據(jù)進(jìn)行驗(yàn)證清洗等知識(shí),完成了用戶的注冊(cè)功能。 既然有登錄登出,那么用戶的注冊(cè)肯定也是少不了的。 注冊(cè)表單類 用戶注冊(cè)時(shí)會(huì)用到表單來(lái)提交賬號(hào)、密碼等數(shù)據(jù),所以需要寫注冊(cè)用的表單/userprofile/forms.py: /userprofile/forms.py .....
摘要:確認(rèn)創(chuàng)建成功后,記得在中注冊(cè)因?yàn)槲覀兿腼@示發(fā)表評(píng)論的時(shí)間,修改時(shí)區(qū)設(shè)置為上海的時(shí)區(qū)。處理錯(cuò)誤請(qǐng)求發(fā)表評(píng)論僅接受請(qǐng)求。返回到一個(gè)適當(dāng)?shù)闹屑从脩舭l(fā)送評(píng)論后,重新定向到文章詳情頁(yè)面。總結(jié)本章實(shí)現(xiàn)了發(fā)表評(píng)論展示評(píng)論的功能。 在沒有互聯(lián)網(wǎng)的年代,我們用日記來(lái)記錄每天的心得體會(huì)。小的時(shí)候我有一個(gè)帶鎖的日記本,生怕被別人看見里面寫了啥,鑰匙藏得那叫一個(gè)絕。 現(xiàn)在時(shí)代變了,網(wǎng)絡(luò)版的日記本:博客,卻巴不...
摘要:語(yǔ)法支持再次打開文件,在文件的最后添加指明了使用語(yǔ)法標(biāo)記,做了兩個(gè)拓展,其中表示支持語(yǔ)法高亮,包含的特性請(qǐng)參見相關(guān)文檔。語(yǔ)法高亮支持注意這一步必須在安裝完主題之后。 目前網(wǎng)上搭建個(gè)人博客的方案很多,雖然使用諸如 Wordpress ( PHP )、Hexo ( Node.js ) 等可以方便快速地搭建一款功能齊全的高性能個(gè)人博客,但是本文將嘗試一種更為小眾化的方案 —— 一款基于 dj...
閱讀 2489·2023-04-26 02:18
閱讀 1275·2021-10-14 09:43
閱讀 3844·2021-09-26 10:00
閱讀 7003·2021-09-22 15:28
閱讀 2555·2019-08-30 15:54
閱讀 2617·2019-08-30 15:52
閱讀 488·2019-08-29 11:30
閱讀 3477·2019-08-29 11:05