摘要:修改某一個組件可能會導致另一個組件出現意想不到的,但是在人工測試時卻很難檢查出來,總不能每寫幾行代碼就把整個網站統統檢查一遍吧。比如說有個功能,限制每個用戶每天發表評論不能超過條,人工測試就顯得比較麻煩,特別是需要反復調試的時候。
測試是伴隨著開發進行的,開發有多久,測試就要多久。本教程已經進行了30多章了,都是如何測試的?當然是runserver啦!每當開發新功能后,都需要運行服務器,假裝自己就是用戶,測試是否運行正常。
這樣的人工測試優點是非常直觀,你看到的和用戶看到的是完全相同的。但是缺點也很明顯:
效率低。在開發時可能你需要反復的修改代碼、測試功能,這樣重復查看幾十次甚至幾百次網頁時會相當的讓人煩躁。
容易遺漏bug。隨著你的項目越來越復雜,組件之間的交互也更加復雜。修改某一個組件可能會導致另一個組件出現意想不到的bug,但是在人工測試時卻很難檢查出來,總不能每寫幾行代碼就把整個網站統統檢查一遍吧。過了很久之后你終于發現了這個bug,但此時你已經搞不清它來源于什么地方了。
有的測試不方便進行。比如說有個功能,限制每個用戶每天發表評論不能超過10條,人工測試就顯得比較麻煩,特別是需要反復調試的時候。
為了解決人工測試的種種問題,Django引入了Python標準庫的單元測試模塊,也就是自動化測試了:你可以寫一段代碼,讓代碼幫你測試!(程序員是最會偷懶的職業..)代碼會忠實的完成測試任務,幫助你從繁重的測試工作中解脫出來。除此之外,自動化測試還有以下優點:
預防錯誤。當應用過于復雜時,代碼的意圖會變得非常不清晰,甚至你都看不懂自己寫的代碼,這是很常見的。而測試就好像是從內部審查代碼一樣,可以幫助你發現微小的錯誤。
有利于團隊協作。良好的測試保證其他人不會不小心破壞了你的代碼(也保證你不會不小心弄壞別人的..)。現在已經不是單打獨斗出英雄的年代了,想要成為優秀的Django程序員,你必須擅長編寫測試!
雖然學習自動化測試不會讓你的博客增加一絲絲的功能,但是可以讓代碼更加強壯,所以我覺得很有必要拿出一章來專門講講。
Django官方文檔的第5部分講測試講得非常的好,并且有中文版本。本章節就大量借鑒了官方文檔,也非常非常推薦讀者去拜讀。第一個測試 給我bug!
為了演示測試是如何工作的,讓我們首先在文章模型中寫個有bug的方法:
article/models.py from django.utils import timezone class ArticlePost(models.Model): ... def was_created_recently(self): # 若文章是"最近"發表的,則返回 True diff = timezone.now() - self.created if diff.days <= 0 and diff.seconds < 60: return True else: return False
這個方法用于檢測當前文章是否是最近發表的。
這個方法稍微擴展一下就會變得非常實用。比如可以將博文的發表日期顯示為“剛剛”、“3分鐘前”、“5小時前”等相對時間,用戶體驗將大有提升。
仔細看看,它是沒辦法正確判斷“未來”的文章的:
>>> import datetime >>> from django.utils import timezone >>> from article.models import ArticlePost >>> from django.contrib.auth.models import User # 創建一篇"未來"的文章 >>> future_article = ArticlePost(author=User(username="user"), title="test",body="test", created=timezone.now() + datetime.timedelta(days=30)) # 是否是“最近”發表的? >>> future_article.was_created_recently() True
未來發生的肯定不是最近發生的,因此代碼是錯誤的。
寫個測試暴露bug接下來就要寫測試用例,將測試轉為自動化。
還記得最初生成文章app時候的目錄結構嗎?
article │ admin.py │ apps.py │ models.py │ tests.py │ views.py │ __init__.py │ └─migrations └─ __init__.py
這個tests.py就是留給你寫測試用例的地方了:
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): # 若文章創建時間為未來,返回 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中的測試代碼抄了過來。有點不同的是末尾這個assertIs方法,了解“斷言”的同學會對它很熟悉:它的作用是檢測方法內的兩個參數是否完全一致,如果不是則拋出異常,提醒你這個地方是有問題滴。
接下來運行測試:
(env) > python manage.py test
運行結果如下:
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"...
這里面名堂就很多了:
首先測試系統會在所有以tests開頭的文件中尋找測試代碼
所有TestCase的子類都被認為是測試代碼
系統創建了一個特殊的數據庫供測試使用,即所有測試產生的數據不會對你自己的數據庫造成影響
類中所有以test開頭的方法會被認為是測試用例
在運行測試用例時,assertIs拋出異常,因為True is not False
完成測試后,自動銷毀測試數據庫
測試系統明確指明了錯誤的數量、位置和種類等信息,請讀者細細品嘗。
修正bug既然通過測試找到了bug,那接下來就要把代碼進行修正:
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
重新運行測試:
(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"...
這次代碼順利通過了測試。
可以肯定的是,在往后的開發中,這個bug不會再出現了,因為你只需要運行一遍測試,就會立即得到警告。可以認為項目的這一小部分代碼永遠是安全的。
更全面的測試既然一個測試用例就可以保證一小段代碼永遠安全,那我寫一堆測試豈不是可以保證整個項目永遠安全嗎?確實如此,這個買賣絕對是不虧的。
因此我們繼續再增加幾個測試,全面強化代碼:
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): # 若文章創建時間為未來,返回 False ... def test_was_created_recently_with_seconds_before_article(self): # 若文章創建時間為 1 分鐘內,返回 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): # 若文章創建時間為幾小時前,返回 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): # 若文章創建時間為幾天前,返回 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)
現在我們擁有了4個測試,來保證was_created_recently()方法對于過去、最近、未來中的4種情況都返回正確的值。你還可以繼續擴展,直到你覺得完全沒有任何bug藏匿的可能性為止。
在實際的開發中,有些難纏的bug會把自己偽裝得非常的好,而不是像教程這樣明確的知道它就在那里。有了自動化測試,無論以后你的項目怎么變化、app交互多么的復雜,只要在測試中寫好的邏輯就一定是符合預期的,而你所需要做的只是運行一條測試指令而已。
雖然教程中僅使用了assertIs,但實際上Django中的斷言有大概幾十種之多,比如assertEqual、assertContains等,并且還在不斷更新。詳見Python標準斷言和Django擴展斷言測試視圖
上面的測試都是針對模型的。視圖該怎么測試?如何通過測試系統模擬出用戶的請求呢?
答案是TestCase類提供了一個供測試使用的Client來模擬用戶通過請求和視圖層代碼的交互。
以文章詳情視圖的瀏覽量統計為例,比較容易出現的潛在bug有:
增加的瀏覽量未能正常保存進數據庫(即每次請求則瀏覽量+1)
增加瀏覽量的同時,updated字段也錯誤的一并更新
所以有針對的寫2條測試。新寫一個專門測試視圖的類,與前面的測試模型的類區分開:
article/tests.py ... from time import sleep from django.urls import reverse class ArticlePostModelTests(TestCase): ... class ArtitclePostViewTests(TestCase): def test_increase_views(self): # 請求詳情視圖時,閱讀量 +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): # 請求詳情視圖時,不改變 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)向視圖發起請求并獲得了響應,剩下的就是從數據庫中取出更新后的數據,并用斷言語句來判斷代碼是否符合預期了。
運行測試:
(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條測試用例全部通過。
越多越好的測試僅僅是app中的兩個非常小的功能,就已經寫了6條測試用例了,并且還可以繼續擴展。除此之外,其他的每個模型、視圖都可以擴展出幾十甚至上百條測試,這樣下去代碼總量很快就要失去控制了,并且相對于業務代碼來說,測試代碼顯得繁瑣且不夠優雅。
但是沒關系!就讓測試代碼繼續肆意增長吧。大部分情況下,你寫完一個測試之后就可以忘掉它了。在你繼續開發的過程中,它會一直默默無聞地為你做貢獻的。最壞的情況是當你繼續開發的時候,發現之前的一些測試現在看來是多余的。但是這也不是什么問題,多做些測試也不錯。
深入代碼測試在前面的測試中,我們已經從模型層和視圖層的角度檢查了應用的輸入輸出,但是模板呢?雖然可以用assertInHTML、assertJSONEqual等斷言大致檢查模板中的某些內容,但更加近似于瀏覽器的檢查就要使用Selenium等測試工具(畢竟Django的重點是后端而不是前端)。
Selenium不僅可以測試 Django 框架里的代碼,甚至還可以檢查 JavaScript代碼。它假裝成是一個正在和你站點進行交互的瀏覽器,就好像有個真人在訪問網站一樣。Django 提供了LiveServerTestCase來和Selenium這樣的工具進行交互。
關于測試的話題這里只是開了個頭,讀者可以繼續閱讀下面的內容進一步了解:
Django: Writing and running tests
Django: Testing tools
Django: Advanced testing topics
Selenium官方文檔
總結有一幫崇尚“測試驅動”的開發者,他們開發時先寫測試代碼,然后才寫業務代碼。而普通開發者通常是先寫業務代碼,再寫測試代碼,這也是沒問題的。但如果你已經寫了很多業務代碼了,再回頭寫測試確實有些無從下手,那么至少在以后寫新功能時,記得加上測試。測試寫得好不好,甚至比功能本身更能看出編程水平。
測試可以讓代碼更加強壯。項目沒出bug時,皆大歡喜,有沒有測試都一樣;一旦出現難纏的bug,你就會無比想念一套完善的測試代碼了。
博主寫自己的網站時就沒有對測試給與足夠的重視,回想起來走了很多彎路。希望讀者以前車之鑒,培養良好的編程習慣。
有疑問請在杜賽的個人網站留言,我會盡快回復。
或Email私信我:dusaiphoto@foxmail.com
項目完整代碼:Django_blog_tutorial
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/45096.html
摘要:比如,在一個博客應用中,你可能會創建如下幾個視圖博客首頁展示最近的幾項內容。這些需求都靠視圖來完成。首先寫一個最簡單的視圖函數,在瀏覽器中打印出字符串。調用函數時會返回一個含字符串的對象。換句話說,的作用是將映射到視圖中。 Django 中的視圖的概念是「一類具有相同功能和模板的網頁的集合」。比如,在一個博客應用中,你可能會創建如下幾個視圖: 博客首頁:展示最近的幾項內容。 內容詳情...
摘要:教程看到這里,你已經學會如下內容搭建開發環境博文管理用戶管理發表評論若干小功能搭建簡單的小博客,以上的功能夠用了。教程為了起步平緩,沒有展開這方面的內容。陌生人,祝你學業進步事業有成歡迎常到杜賽的個人網站做客 教程看到這里,你已經學會如下內容: 搭建開發環境 博文管理 用戶管理 發表評論 若干小功能 搭建簡單的小博客,以上的功能夠用了。 相信你的志向不止于此。畢竟程序員面試個個造火...
摘要:既然有登錄登出,那么用戶的注冊肯定也是少不了的。用戶在注冊成功后會自動登錄并返回博客列表頁面。總結本章用到了表單類對數據進行驗證清洗等知識,完成了用戶的注冊功能。 既然有登錄登出,那么用戶的注冊肯定也是少不了的。 注冊表單類 用戶注冊時會用到表單來提交賬號、密碼等數據,所以需要寫注冊用的表單/userprofile/forms.py: /userprofile/forms.py .....
摘要:確認創建成功后,記得在中注冊因為我們想顯示發表評論的時間,修改時區設置為上海的時區。處理錯誤請求發表評論僅接受請求。返回到一個適當的中即用戶發送評論后,重新定向到文章詳情頁面。總結本章實現了發表評論展示評論的功能。 在沒有互聯網的年代,我們用日記來記錄每天的心得體會。小的時候我有一個帶鎖的日記本,生怕被別人看見里面寫了啥,鑰匙藏得那叫一個絕。 現在時代變了,網絡版的日記本:博客,卻巴不...
摘要:語法支持再次打開文件,在文件的最后添加指明了使用語法標記,做了兩個拓展,其中表示支持語法高亮,包含的特性請參見相關文檔。語法高亮支持注意這一步必須在安裝完主題之后。 目前網上搭建個人博客的方案很多,雖然使用諸如 Wordpress ( PHP )、Hexo ( Node.js ) 等可以方便快速地搭建一款功能齊全的高性能個人博客,但是本文將嘗試一種更為小眾化的方案 —— 一款基于 dj...
閱讀 2461·2023-04-26 02:18
閱讀 1262·2021-10-14 09:43
閱讀 3822·2021-09-26 10:00
閱讀 6945·2021-09-22 15:28
閱讀 2535·2019-08-30 15:54
閱讀 2600·2019-08-30 15:52
閱讀 474·2019-08-29 11:30
閱讀 3465·2019-08-29 11:05