該系列教程系個人原創,並完整發布在個人官網劉江的博客和教程
所有轉載本文者,需在頂部顯著位置注明原作者及www.liujiangblog.com官網地址。
本節將簡要介紹Django的自動化測試相關內容。
一、自動化測試概述
什么是自動化測試
測試是一種例行的、不可缺失的工作,用於檢查你的程序是否符合預期。
測試可以划分為不同的級別。一些測試可能專注於小細節(比如某一個模型的方法是否會返回預期的值?), 一些測試則專注於檢查軟件的整體運行是否正常(用戶在對網站進行了一系列的輸入后,是否返回了期望的結果?)。
測試可以分為手動測試和自動測試。手動測試很常見,有時候print一個變量內容,都可以看做是測試的一部分。手動測試往往很零碎、不成體系、不夠完整、耗時費力、效率低下,測試結果也不一定准確。
自動化測試則是系統地較為完整地對程序進行測試,效率高,准確性高,並且大部分共同的測試工作會由系統來幫你完成。一旦你創建了一組自動化測試程序,當你修改了你的應用,你就可以用這組測試程序來檢查你的代碼是否仍然同預期的那樣運行,而無需執行耗時的手動測試。
為什么需要測試?
大家都明白:
- 測試可以節省你的時間
- 測試不僅僅可以發現問題,還能防止問題
- 測試使你的代碼更受歡迎
- 測試有助於團隊合作
二、編寫測試程序
Django是一個全面、完善、嚴謹的Web框架,當然不會缺少測試功能。
1.遇見BUG
很巧,在我們的投票應用中有一個小bug需要修改:在Question.was_published_recently()方法的返回值中,當Qeustion在最近的一天發布的時候返回True(這是正確的),然而當Question在未來的日期內發布的時候也返回True(這是錯誤的)。
我們可以在admin后台創建一個發布日期在未來的Question,然后在shell中驗證這個bug:
>>> import datetime
>>> from django.utils import timezone
>>> from polls.models import Question
>>> # 創建一個發布日期在30天后的問卷
>>> future_question = Question(pub_date=timezone.now() + datetime.timedelta(days=30))
>>> # 測試一下返回值
>>> future_question.was_published_recently()
True
問題的核心在於我們允許創建在未來時間才發布的問卷,由於“未來”不等於“最近”,因此這顯然是個bug。
2.創建一個測試來暴露這個bug
剛才我們是在shell中測試了這個bug,那如何通過自動化測試來發現這個bug呢?
通常,我們會把測試代碼放在應用的tests.py文件中,測試系統將自動地從任何名字以test開頭的文件中查找測試程序。每個app在創建的時候,都會自動創建一個tests.py文件,就像views.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):
"""
在將來發布的問卷應該返回False
"""
time = timezone.now() + datetime.timedelta(days=30)
future_question = Question(pub_date=time)
self.assertIs(future_question.was_published_recently(), False)
我們在這里創建了一個django.test.TestCase的子類,它具有一個方法,該方法創建一個pub_date在未來的Question實例。最后我們檢查was_published_recently()的輸出,它應該是 False。
3.運行測試程序
在終端中,運行下面的命令,
$ python manage.py test polls
你將看到結果如下:
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'...
這其中都發生了些什么?:
python manage.py test polls命令會查找投票應用中所有的測試程序- 發現一個
django.test.TestCase的子類 - 為測試創建一個專用的數據庫
- 查找名字以
test開頭的測試方法 - 在
test_was_published_recently_with_future_question方法中,創建一個Question實例,該實例的pub_data字段的值是30天后的未來日期。 - 然后利用
assertIs()方法,它發現was_published_recently()返回了True,而不是我們希望的False。
最后,測試程序會通知我們哪個測試失敗了,錯誤出現在哪一行。
整個測試用例基本上和Python內置的unittest非常相似,大家可以參考Python教程中測試相關的章節。
3.修復bug
我們已經知道了問題所在,現在可以去修復bug了。修改源代碼,具體如下:
# polls/models.py
def was_published_recently(self):
now = timezone.now()
return now - datetime.timedelta(days=1) <= self.pub_date <= now
再次運行測試程序:
Creating test database for alias 'default'...
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
Destroying test database for alias 'default'...
可以看到bug已經沒有了。
4.更加全面的測試
事實上,前面的測試用例還不夠完整,為了使was_published_recently()方法更加可靠,我們在上面的測試類中再額外添加兩個其它的方法,來更加全面地進行測試。
# polls/tests.py
def test_was_published_recently_with_old_question(self):
"""
只要是超過1天的問卷,返回False
"""
time = timezone.now() - datetime.timedelta(days=1, seconds=1)
old_question = Question(pub_date=time)
self.assertIs(old_question.was_published_recently(), False)
def test_was_published_recently_with_recent_question(self):
"""
最近一天內的問卷,返回True
"""
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)
現在我們有三個測試來保證無論發布時間是在過去、現在還是未來Question.was_published_recently()都將返回正確的結果。
關於測試的內容,雖然很重要,但是對於剛入門者而言卻不是最首要的知識點,等將主體內容掌握后,我們再回頭來梳理這一部分。
