天天看點

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

1 學習目标

  1. 能夠使用代碼實作藍圖對項目進行子產品化
  2. 能夠說出斷言的作用
  3. 能夠說出實作單元測試步驟
  4. 能夠說出單元測試所執行方法的定義規則

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 循環引入

建立項目:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

建立demo:(main.py 主檔案)

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

如果将所有的視圖函數都定義在一個py檔案中,那以後将難以維護,是以我們這裡可以考慮分子產品,如下:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

将order_list放入另一個py檔案中:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

但是出問題了:我們運作main.py 結果url_map中沒有order_list的url映射:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

那怎麼辦,導入呗:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

再次運作,發現報錯了:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

因為出現了循環導入(循環引用)問題:main中導入order,并且 order中導入main,如下圖:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

2.4.2 使用藍圖

使用藍圖解決問題:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

main中導入藍圖,注冊藍圖:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

運作:發現ok

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

2.4.3 總結

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試
藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

2.5 子產品形式使用藍圖

一般藍圖我們都是一個子產品。

建立一個包:cart  (python package)

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

将main中的cart_list分離到cart子產品中

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

cart子產品中是有藍圖的,我們就在init中建立藍圖:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

注意:這裡要将視圖函數中的所有内容導入,要不然目前藍圖是不知道都有哪些視圖函數的。

那将函數放哪呢? 我們再建立一個views.py 專門放視圖函數:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

在main中注冊藍圖:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

注:從 cart 中導入 cart_blu 時,可能會報錯,解決方案:将Flask_day04_03_blueprint 檔案夾設定為 Sources Root 

(選中檔案夾右擊 --> Mark Directory as --> Sources Root)

運作:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

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

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

那如何通路呢?

我們來看下藍圖的源碼(點選BluePrint()):(發現藍圖沒有預設指定static_folder和其他的)

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

但是flask預設指定了(點選Flask()):

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

而url_map中也隻有一個static路徑,指向的也是flask的靜态檔案夾:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

是以我們去通路的時候,是找不到的:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

那既然Blueprint沒有預設指定static_folder,那我們就指定呗:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

你會發現雖然有了,但是藍圖的與app的靜态url竟然一樣的:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

再次通路還是找不到(因為先比對的app的static,但是沒有a.png):

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

怎麼辦? 我們需要區分app和藍圖的url,建立藍圖時指定一個url字首(url_prefix):

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

既然有字首了,綁定url映射的時候,就不用再加字首了:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

運作:(發現終于不一樣了)

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

通路:ok

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

2.9 模闆

接下來我們順便讨論一下模闆,藍圖中建立模闆檔案夾templates,模闆檔案cart.html:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

視圖函數渲染:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

通路:但是找不到

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

這是因為藍圖也沒有預設指定模闆檔案夾,指定一下即可(和靜态檔案通路類似):

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

通路,沒問題:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

模闆的優先級

如果app中也有相同的模闆:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

通路:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

發現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 斷言方法的使用

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

看下圖黃箭頭:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

斷言語句類似于:

if not expression:    
    raise AssertionError
AssertionError
           

3.2.2 例子

建立項目:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

建立demo:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

如果我們在調用div方法時傳參錯誤,導緻報錯.。這時候我們作為優秀的程式員,應該讓我們定義的方法更加完善。如果給div傳遞錯誤的參數,我們的程式應該給予友好提示,如下圖:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

看到下邊的錯誤提示,是一個AssertionError,這就是我們給出的斷言錯誤。這樣就ok了,使用div的程式員就可以很清楚的知道原來是他傳遞的參數有問題。

斷言解釋:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

如果斷言後邊跟上的表達式為真,則斷言成功;

如果後邊的表達式為假,則斷言失敗,程式停止運作,傳回斷言錯誤: “值類型不正确”

完善一下這個斷言:增加除數為0情況

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

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來測試一下,如果沒有參數:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

如果使用者名密碼錯誤:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

如果使用者名密碼正确:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

測試都通過。

3.3.3 單元測試

接下來我們通過代碼來做單元測試:

建立單元測試demo2_unittest.py:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

上述代碼說明:

app.test_client().post() 可以發起post請求:第一個參數是請求url,第二個參數是傳遞的資料

傳回值為response

response.data就是擷取到視圖函數傳回的資料

如下:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

視圖函數傳回的都是json資料,我們就可以把json資料轉換為字典使用:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

繼續分析代碼:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

assertIsNotNone,是判斷内容不為None.

assertIn,是判斷errorcode是否在json_dcit中.

assertEqual,是判斷errcode是否等于-2.

我們再來看一下單元測試中的setUp方法:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

再來看testing:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

如果将app.testing 設定為 False:碰巧我們要測試的代碼中有bug

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

測試:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

發現報錯資訊有點不對頭,不是應該抛出被測試的代碼中具體哪裡錯誤麼?

但是這裡如果不是testing模式的話,就隻會報出自己哪一行報錯了,不具體

那怎麼辦?  設定testing = True

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

如下:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

這是就可以看到具體錯誤的位置了 

3.4 常用的斷言方法

assertEqual     如果兩個值相等,則pass
assertNotEqual  如果兩個值不相等,則pass
assertTrue      判斷bool值為True,則pass
assertFalse     判斷bool值為False,則pass
assertIsNone    不存在,則pass
assertIsNotNone 存在,則pass
           

3.5 資料庫的測試

将之前的圖書作者案例拿過來,如下:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

建立測試demo:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

要做的事情:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

代碼:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試
藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

測試如下:

藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試
藍圖&單元測試1 學習目标2 藍圖(Blueprint)3 單元測試

繼續閱讀