藍圖&單元測試
1 學習目標
- 能夠使用程式碼實現藍圖對專案進行模組化
- 能夠說出斷言的作用
- 能夠說出實現單元測試步驟
- 能夠說出單元測試所執行方法的定義規則
2 藍圖(Blueprint)
2.1 模組化
隨著flask程式越來越複雜,我們需要對程式進行模組化的處理,之前學習過python的模組化管理,於是針對一個簡單的flask程式進行模組化處理。但是發現.py檔案直接報錯,程式碼無法繼續寫下去,所以在flask程式中,使用傳統的模組化是行不通的,需要flask提供一個特有的模組化處理方式,lask內建了一個模組化處理的類,即Blueprint
2.2 Blueprint 概念
簡單來說,Blueprint 是一個儲存操作方法的容器,這些操作在這個Blueprint 被註冊到一個應用之後就可以被呼叫,Flask 可以通過Blueprint來組織URL以及處理請求。
Flask使用Blueprint讓應用實現模組化,在Flask中,Blueprint具有如下屬性:
- 一個應用可以具有多個Blueprint
- 可以將一個Blueprint註冊到任何一個未使用的URL下,比如 “/”、“/sample”或者子域名
- 在一個應用中,一個模組可以註冊多次
- Blueprint可以單獨具有自己的模板、靜態檔案或者其它的通用操作方法,它並不是必須要實現應用的檢視和函式的
- 在一個應用初始化時,就應該要註冊需要使用的Blueprint
但是一個Blueprint並不是一個完整的應用,它不能獨立於應用執行,而必須要註冊到某一個應用中。
2.3 初識藍圖
藍圖/Blueprint物件用起來和一個應用/Flask物件差不多,最大的區別在於一個藍圖物件沒有辦法獨立執行,必須將它註冊到一個應用物件上才能生效
使用藍圖可以分為三個步驟
- 1. 建立一個藍圖物件
admin=Blueprint('admin',__name__)
- 2. 在這個藍圖物件上進行操作,註冊路由,指定靜態資料夾,註冊模版過濾器
@admin.route('/') def admin_home(): return 'admin_home'
- 3. 在應用物件上註冊這個藍圖物件
app.register_blueprint(admin,url_prefix='/admin')
當這個應用啟動後,通過/admin/可以訪問到藍圖中定義的檢視函式
2.4 藍圖例子
2.4.1 迴圈引入
新建專案:
新建demo:(main.py 主檔案)
如果將所有的檢視函式都定義在一個py檔案中,那以後將難以維護,所以我們這裡可以考慮分模組,如下:
將order_list放入另一個py檔案中:
但是出問題了:我們執行main.py 結果url_map中沒有order_list的url對映:
那怎麼辦,匯入唄:
再次執行,發現報錯了:
因為出現了迴圈匯入(迴圈引用)問題:main中匯入order,並且 order中匯入main,如下圖:
2.4.2 使用藍圖
使用藍圖解決問題:
main中匯入藍圖,註冊藍圖:
執行:發現ok
2.4.3 總結
2.5 模組形式使用藍圖
一般藍圖我們都是一個模組。
新建一個包:cart (python package)
將main中的cart_list分離到cart模組中
cart模組中是有藍圖的,我們就在init中新建藍圖:
注意:這裡要將檢視函式中的所有內容匯入,要不然當前藍圖是不知道都有哪些檢視函式的。
那將函式放哪呢? 我們再新建一個views.py 專門放檢視函式:
在main中註冊藍圖:
注:從 cart 中匯入 cart_blu 時,可能會報錯,解決方案:將Flask_day04_03_blueprint 資料夾設定為 Sources Root
(選中資料夾右擊 --> Mark Directory as --> Sources Root)
執行:
2.6 執行機制
- 藍圖是儲存了一組將來可以在應用物件上執行的操作,註冊路由就是一種操作
- 當在應用物件上呼叫 route 裝飾器註冊路由時,這個操作將修改物件的url_map路由表
- 然而,藍圖物件根本沒有路由表,當我們在藍圖物件上呼叫route裝飾器註冊路由時,它只是在內部的一個延遲操作記錄列表defered_functions中添加了一個項
- 當執行應用物件的 register_blueprint() 方法時,應用物件將從藍圖物件的 defered_functions 列表中取出每一項,並以自身作為引數執行該匿名函式,即呼叫應用物件的 add_url_rule() 方法,這將真正的修改應用物件的路由表
2.7 藍圖的url字首
- 當我們在應用物件上註冊一個藍圖時,可以指定一個url_prefix關鍵字引數(這個引數預設是/)
-
在應用最終的路由表 url_map中,在藍圖上註冊的路由URL自動被加上了這個字首,這個可以保證在多個藍圖中使用相同的URL規則而不會最終引起衝突,只要在註冊藍圖時將不同的藍圖掛接到不同的自路徑即可
-
url_for
url_for('admin.index') # /admin/
2.8 藍圖的靜態檔案及靜態檔案訪問(註冊靜態路由)
新建static,並且放入圖片a.png
那如何訪問呢?
我們來看下藍圖的原始碼(點選BluePrint()):(發現藍圖沒有預設指定static_folder和其他的)
但是flask預設指定了(點選Flask()):
而url_map中也只有一個static路徑,指向的也是flask的靜態資料夾:
所以我們去訪問的時候,是找不到的:
那既然Blueprint沒有預設指定static_folder,那我們就指定唄:
你會發現雖然有了,但是藍圖的與app的靜態url竟然一樣的:
再次訪問還是找不到(因為先匹配的app的static,但是沒有a.png):
怎麼辦? 我們需要區分app和藍圖的url,建立藍圖時指定一個url字首(url_prefix):
既然有字首了,繫結url對映的時候,就不用再加字首了:
執行:(發現終於不一樣了)
訪問:ok
2.9 模板
接下來我們順便討論一下模板,藍圖中新建模板資料夾templates,模板檔案cart.html:
檢視函式渲染:
訪問:但是找不到
這是因為藍圖也沒有預設指定模板資料夾,指定一下即可(和靜態檔案訪問類似):
訪問,沒問題:
模板的優先順序
如果app中也有相同的模板:
訪問:
發現app的優先順序大於藍圖的。
注:如果在 templates 中存在和 cart/templates 同名檔案,則系統會優先使用 templates 中的檔案 參考連結:https://stackoverflow.com/questions/7974771/flask-blueprint-template-folder
3 單元測試
3.1 為什麼要測試?
Web程式開發過程一般包括以下幾個階段:[需求分析,設計階段,實現階段,測試階段]。其中測試階段通過人工或自動來執行測試某個系統的功能。目的是檢驗其是否滿足需求,並得出特定的結果,以達到弄清楚預期結果和實際結果之間的差別的最終目的。
3.1.2 測試的分類:
測試從軟體開發過程可以分為:
- 單元測試
- 對單獨的程式碼塊(例如函式)分別進行測試,以保證它們的正確性
- 整合測試
- 對大量的程式單元的協同工作情況做測試
- 系統測試
- 同時對整個系統的正確性進行檢查,而不是針對獨立的片段
在眾多的測試中,與程式開發人員最密切的就是單元測試,因為單元測試是由開發人員進行的,而其他測試都由專業的測試人員來完成。所以我們主要學習單元測試。
3.1.3 什麼是單元測試?
程式開發過程中,寫程式碼是為了實現需求。當我們的程式碼通過了編譯,只是說明它的語法正確,功能能否實現則不能保證。 因此,當我們的某些功能程式碼完成後,為了檢驗其是否滿足程式的需求。可以通過編寫測試程式碼,模擬程式執行的過程,檢驗功能程式碼是否符合預期。
單元測試就是開發者編寫一小段程式碼,檢驗目的碼的功能是否符合預期。通常情況下,單元測試主要面向一些功能單一的模組進行。
舉個例子:一部手機有許多零部件組成,在正式組裝一部手機前,手機內部的各個零部件,CPU、記憶體、電池、攝像頭等,都要進行測試,這就是單元測試。
在Web開發過程中,單元測試實際上就是一些“斷言”(assert)程式碼。
斷言就是判斷一個函式或物件的一個方法所產生的結果是否符合你期望的那個結果。 python中assert斷言是宣告布林值為真的判定,如果表示式為假會發生異常。單元測試中,一般使用assert來斷言結果。
3.2 斷言的介紹
3.2.1 斷言方法的使用
看下圖黃箭頭:
斷言語句類似於:
if not expression:
raise AssertionError
AssertionError
3.2.2 例子
新建專案:
新建demo:
如果我們在呼叫div方法時傳參錯誤,導致報錯.。這時候我們作為優秀的程式設計師,應該讓我們定義的方法更加完善。如果給div傳遞錯誤的引數,我們的程式應該給予友好提示,如下圖:
看到下邊的錯誤提示,是一個AssertionError,這就是我們給出的斷言錯誤。這樣就ok了,使用div的程式設計師就可以很清楚的知道原來是他傳遞的引數有問題。
斷言解釋:
如果斷言後邊跟上的表示式為真,則斷言成功;
如果後邊的表示式為假,則斷言失敗,程式停止執行,返回斷言錯誤: “值型別不正確”
完善一下這個斷言:增加除數為0情況
3.3 單元測試編寫
3.3.1 準備工作
先寫一個要被測試的demo2:
from flask import Flask, jsonify
from flask import request
app = Flask(__name__)
@app.route('/')
def index():
return 'index'
# 使用者登入
# 1. 如果傳入的引數不足,會返回 errcode = -2
# 2. 如果傳入的賬號與密碼不正確會返回 errcode = -1
# 3. 如果賬號與密碼都正確, errcode = 0
@app.route('/login', methods=['POST'])
def login():
username = request.form.get('username')
password = request.form.get('password')
# 判斷引數是否為空
if not all([username, password]):
result = {
"errcode": -2,
"errmsg": "params error"
}
return jsonify(result)
# 如果賬號密碼正確
# 判斷賬號密碼是否正確
if username == 'itheima' and password == 'python':
result = {
"errcode": 0,
"errmsg": "success"
}
return jsonify(result)
else:
result = {
"errcode": -1,
"errmsg": "wrong username or password"
}
return jsonify(result)
if __name__ == '__main__':
app.run(debug=True)
接下來我們來測試一下這些邏輯。
3.3.2 postman 測試
我們先通過postman來測試一下,如果沒有引數:
如果使用者名稱密碼錯誤:
如果使用者名稱密碼正確:
測試都通過。
3.3.3 單元測試
接下來我們通過程式碼來做單元測試:
新建單元測試demo2_unittest.py:
上述程式碼說明:
app.test_client().post() 可以發起post請求:第一個引數是請求url,第二個引數是傳遞的資料
返回值為response
response.data就是獲取到檢視函式返回的資料
如下:
檢視函式返回的都是json資料,我們就可以把json資料轉換為字典使用:
繼續分析程式碼:
assertIsNotNone,是判斷內容不為None.
assertIn,是判斷errorcode是否在json_dcit中.
assertEqual,是判斷errcode是否等於-2.
我們再來看一下單元測試中的setUp方法:
再來看testing:
如果將app.testing 設定為 False:碰巧我們要測試的程式碼中有bug
測試:
發現報錯資訊有點不對頭,不是應該丟擲被測試的程式碼中具體哪裡錯誤麼?
但是這裡如果不是testing模式的話,就只會報出自己哪一行報錯了,不具體
那怎麼辦? 設定testing = True
如下:
這是就可以看到具體錯誤的位置了
3.4 常用的斷言方法
assertEqual 如果兩個值相等,則pass
assertNotEqual 如果兩個值不相等,則pass
assertTrue 判斷bool值為True,則pass
assertFalse 判斷bool值為False,則pass
assertIsNone 不存在,則pass
assertIsNotNone 存在,則pass
3.5 資料庫的測試
將之前的圖書作者案例拿過來,如下:
新建測試demo:
要做的事情:
程式碼:
測試如下: