Flask應(yīng)用測試
Something that is untested is broken.
上面這句話并不完全正確嘴办,但是距離事實也不遠(yuǎn)了膝迎,雖然不知道這句話從哪兒來的。
未經(jīng)測試的應(yīng)用瞄沙,很難去改善它的代碼,未經(jīng)測試的應(yīng)用程序的開發(fā)人員往往變得非常偏執(zhí)。
如果應(yīng)用程序具有自動測試功能品姓,您可以安全地進(jìn)行更改,并立即知道是否有任何故障箫措。
Flask提供了一種通過暴露Werkzeug測試客戶端腹备,并為您處理上下文本地來測試應(yīng)用程序的方法。
你可以使用它搭建自己喜歡的測試方案斤蔓。在這篇文檔里植酥,我們使用Python自帶的unittest包。
應(yīng)用程序
首先弦牡,我們需要有一個待測試的應(yīng)用程序友驮,我們使用在Tutorial
教程中編寫的程序。如果你還沒有這個程序驾锰,請從這里下載the examples
測試骨架
為了便于測試卸留,我們增加第二個模塊flaskr_tests.py,并且創(chuàng)建一個單元測試的骨架:
import os
import flaskr
import unittest
import tempfile
class FlaskrTestCase(unittest.TestCase):
def setUp(self):
self.db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp()
flaskr.app.testing = True
self.app = flaskr.app.test_client()
with flaskr.app.app_context():
flaskr.init_db()
def tearDown(self):
os.close(self.db_fd)
os.unlink(flaskr.app.config['DATABASE'])
if __name__ == '__main__':
unittest.main()
在setUp()中的代碼創(chuàng)建了一新的測試客戶端椭豫,并且初始化了一個數(shù)據(jù)庫耻瑟。
在每個獨立的測試程序運行之前,這個函數(shù)就被執(zhí)行了赏酥。為了在測試之后刪除數(shù)據(jù)庫喳整,我們在tearDown()
方法中關(guān)閉文件,并且移除裸扶。此外框都,在建立的時候,TESTING配置旗桿也被激活了呵晨。它所做的就是阻止
請求處理時候的錯誤捕捉魏保,使你在執(zhí)行測試請求的時候可以得到更好的錯誤報告蔗蹋。
這個測試端口將會為我們提供一個簡單的程序接口。我們可以觸發(fā)對程序的請求囱淋,并且端口也會為我們跟蹤cookies猪杭。
因為SQLite3是基于文件系統(tǒng)的,我們可以輕易的使用tempfile模塊妥衣,去創(chuàng)建一個臨時的數(shù)據(jù)庫并進(jìn)行初始化皂吮。
mkstemp()函數(shù)為我們做了兩件事:它返回一個底層的文件句柄,和一個隨機(jī)的文件名税手,這個名字是我們將要用來創(chuàng)建數(shù)據(jù)庫的名字蜂筹。
我們必須始終保存 db_fd 以便于我們可以使用os.close()函數(shù)關(guān)閉文件。
如果現(xiàn)在我們運行測試的話芦倒,我們將會看到如下輸出:
python flaskr_tests.py
------------------------------------------------
Ran 0 tests in 0.000s
OK
即使這沒有做任何真正的測試艺挪,我們也已經(jīng)知道了這個flaskr應(yīng)用是語法上有效的,否則兵扬,這樣的引入會產(chǎn)生異常而終止麻裳。
第一個測試
現(xiàn)在是時候開始測試應(yīng)用程序的功能了.現(xiàn)在我們測試,如果我們進(jìn)入應(yīng)用程序的根目錄器钟,就會顯示“No entries here so far”
因此津坑,我們向類中增加一個方法,如下:
class FlaskrTestCase(unittest.TestCase):
def setUp(self):
self.db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp()
flaskr.app.testing = True
self.app = flaskr.app.test_client()
with flaskr.app.app_context():
flaskr.init_db()
def tearDown(self):
os.close(self.db_fd)
os.unlink(flaskr.app.config['DATABASE'])
def test_empty_db(self):
rv = self.app.get('/')
assert b'No entries here so far' in rv.data
請注意傲霸,我們的測試功能以測試文字開始;這允許unittest自動將方法識別為運行測試疆瑰。
通過使用self.app.get('/')我們可以用特定的路徑,向應(yīng)用程序發(fā)送一個HTTP請求昙啄。
返回值是一個response_class對象∧乱郏現(xiàn)在我們可以使用data屬性,檢驗從應(yīng)用程序的返回值(字符串)梳凛。
在這種情況下耿币,我們確保‘No entries here so far’是輸出的一部分伶跷。
再次運行它掰读,你將會看到顯示通過測試:
python flaskr_tests.py
------------------------------------
Ran 1 test in 0.034s
OK
登陸和退出
我們應(yīng)用的大部分只對于管理員才有用秘狞,所以我們需要一種方式叭莫,讓我們測試的客戶可以登錄、推出烁试。
為了這樣做雇初,我們使用所需的表單數(shù)據(jù)(用戶名和密碼)將一些請求發(fā)送到登錄和注銷頁面。
并且因為登錄和注銷頁面重定向减响,我們告訴客戶follow_redirects靖诗。
向FlaskrTestCase類增加如下兩個方法:
def login(self, username, password):
return self.app.post('/login', data=dict(
username = username,
password = password
), follow_redirects=True)
def logout(self):
return self.app.get('/logout', follow_redirects=True)
現(xiàn)在我們可以輕松地測試登陸和退出的功能郭怪,并且,對于無用的數(shù)據(jù)刊橘,會顯示失敗鄙才。
現(xiàn)在把新的測試添加到類中:
def test_login_logout(self):
rv = self.login('admin', 'default')
assert b'You were logged in' in rv.data
rv = self.logout()
assert b'You were logged out' in rv.data
rv = self.login('adminx', 'default')
assert b'Invalid username' in rv.data
rv = self.login('admin', 'defaultx')
assert b'Invalid passward' in rv.data
添加消息測試
我們還應(yīng)該測試添加消息是否有效。像下面這樣添加一個測試方法:
def test_message(self):
self.login('admin', 'default')
rv = self.app.post('/add', data=dict(
title = '<Hello>',
text = '<strong>HTML</strong> allowed here'
), follow_redirects=True)
assert b'No entries here so far' not in rv.data
assert b'< Hello>' in rv.data
assert b'<strong>HTML</strong> allowed here' in rv.data
這里我們測試HTML可以存在于文本中促绵,但是不能在標(biāo)題中攒庵,這是intended behavior.
運行測試,我們得到3個測試成功的結(jié)果:
python flaskr_tests.py
...
---------------------------------
Ran 3 tests in 0.332s
OK
如果想測試更多關(guān)于返回頭败晴,和返回狀態(tài)浓冒,請查看MiniTwit Example
包含了許多測試套件。
其他一些測試技巧
除了上面介紹的用戶測試以外尖坤,還有另外一種測試方法稳懒,使用test_request_context()方法和with語句
一起進(jìn)行測試,以適當(dāng)?shù)姆椒せ钫埱舐丁_@種方法可以使你獲得request,g,session對象场梆,就像在試圖函數(shù)中
一樣。這是一個充分演示這種方法的例子: