Table
一. 環境設置
Flask 套件選擇和安裝
此次選擇使用的是 Flask-Testing,此套件將 Python 內建的 Unittest 進行封裝,相較於 pytest 優點在建立 create_app 的時候非常簡單易懂。
Flask-Testing — Flask-Testing 0.3 documentation
1 |
pip3 install Flask-Testing |
此次架構配置
會在專案中建立新資料夾 tests,並將所有的單元測試 py 檔放置於此資料夾內,此次架構上會搭配 Flask 工廠模式使用。
有關於 flask 工廠模式的好處和使用方法,可以參考此篇:
【Flask 教學】實作 Flask Application Factories 工廠模式 | Max行銷誌
1 2 3 4 5 6 7 8 9 |
└── flask ├── app │ ├── __init__.py │ ├── config │ ├── model │ └── view ├── main.py └── tests └── test_auth.py |
二. 進入主題 Flask 實作單元測試
實作步驟一. 配置 main.py
我們將使用 Flask 的 CLI (Command Line Interface — Flask Documentation (1.1.x) ),CLI 的好處是可以在終端機中運行設定好的 Flask 指令,如下範例程式將命名函式為 test,所以我們只要在終端機中,先 cd
到 main.py 的位置,再下指令 flask test
就會執行以下設定好的指令,開始運行所有測試囉!
main.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
from flask_migrate import Migrate, upgrade, migrate from app import create_app, db app = create_app('development') migrates = Migrate(app=app, db=db) @app.cli.command() def test(): import unittest import sys tests = unittest.TestLoader().discover("tests") result = unittest.TextTestRunner(verbosity=2).run(tests) if result.errors or result.failures: sys.exit(1) |
實作步驟二. 配置 test 檔
只要將想要運行的 py 檔,放置於此 tests 文件中,待會運行指令時就會被運行到,以下我們將寫一個範例 test_auth.py 檔,此份將會測試使用者登入的 api 運行。
tests/test_auth.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
import unittest from flask import url_for from flask_testing import TestCase from app import create_app, db import json class SettingBase(TestCase): def create_app(self): return create_app("testing") # 在運行測試之前會先被執行 def setUp(self): db.create_all() self.passwords = "666666" self.role = 0 # 在結束測試時會被執行 def tearDown(self): db.session.remove() # signup 是測試時很常會被用到的功能,所以寫成函式,可以重複利用 def signup(self): response = self.client.post(url_for('api.users'), follow_redirects=True, json={ "username": self.username, "password": self.passwords, "role": self.role }) return response # 這邊繼承剛剛的寫的 SettingBase class,接下來會把測試都寫在這裡 class CheckUserAndLogin(SettingBase): def test_signup(self): response = self.signup() self.assertEqual(response.status_code, 200) def test_signup_400(self): # 測試密碼少於六位數 self.passwords = '123' response = self.signup() self.assertEqual(response.status_code, 400) def test_signup_422(self): # 測試重複註冊 response = self.signup() response = self.signup() self.assertEqual(response.status_code, 422) if __name__ == '__main__': unittest.main() |
解析 1. 實例化 Flask instance
我們使用 Flask 工廠模式,所以從 app/__init__.py
中 import create_app,所以只需要 return create_app("testing")
1 2 |
def create_app(self): return create_app("testing") |
如果不使用工廠模式的話,就需要實例化的 app = Flask(__name__)
,並設定 config 參數
1 2 3 4 |
def create_app(self): app = Flask(__name__) app.config['TESTING'] = True return app |
解析 2. 測試前後工作準備
此外我們在 SettingBase 的 class 中有額外設定 setUp() 和 tearDown() 方法,setUp()
代表在運行測試之前會先被執行,而 tearDown()
代表在結束測試時會被執行。
1 2 3 4 5 6 7 8 9 10 11 |
# 在運行測試之前會先被執行 def setUp(self): db.create_all() self.passwords = "666666" self.role = 0 # 在結束測試時會被執行 def tearDown(self): db.session.remove() |
解析 3. 斷言 Assertion 的使用方法
- assertEqual(a, b):a == b
- assertNotEqual(a, b):a != b
- assertTrue(x):bool(x) is True
- assertFalse(x):bool(x) is False
- assertIs(a, b):a is b
- assertIsNot(a, b):a is not b
- assertIsNone(x):x is None
- assertIsNotNone(x):x is not None
- assertIn(a, b):a in b
- assertNotIn(a, b):a not in b
- response.data:看回傳「結果」
- response.status_code:想看回傳「狀態」
更多詳細 assertIs 指令可以參考此篇:unittest — 單元測試框架 — Python 3.8.5 說明文件
實作步驟三. 運行測試
因為我們有在 main.py 寫入 CLI,所以只要終端機與 /flask/main.py
同位置,只需要下指令 flask test
就會開始運行測試
1 2 3 4 5 6 7 8 9 |
@app.cli.command() def test(): import unittest import sys tests = unittest.TestLoader().discover("tests") result = unittest.TextTestRunner(verbosity=2).run(tests) if result.errors or result.failures: sys.exit(1) |
運行結果如下:
1 2 3 4 5 6 7 8 |
test_signup (test_auth.CheckUserAndLogin) ... ok test_signup_400 (test_auth.CheckUserAndLogin) ... ok test_signup_422 (test_auth.CheckUserAndLogin) ... ok ---------------------------------------------------------------------- Ran 3 tests in 0.402s OK |
補充. 覆蓋率測試
在終端機輸入以下指令,和剛剛一樣他會先偵測 /tests
裡面的所有的 py 檔,然後開始運行每一份測試檔,並將報告結果存在 /tests/test.coveragerc
1 2 3 |
coverage run --rcfile= /tests/test.coveragerc -m unittest discover -s tests/ coverage report -m coverage html --rcfile= /tests/test.coveragerc |
運行結果
1 2 3 4 5 6 7 8 9 10 |
... ---------------------------------------------------------------------- Ran 3 tests in 0.465s OK Name Stmts Miss Cover Missing ------------------------------------------------------------- flask/tests/test_auth.py 3 0 100% ------------------------------------------------------------- TOTAL 3 0 100% |
補充:單元測試 Unit Test 原則
以下原則參考:Test F.I.R.S.T | Hacker Noon
F.I.R.S.T 原則 (F.I.R.S.T Principles of Unit Testing)
1. Fast – 快速
2. Independent – 獨立,測試之間要相互獨立,如果互相依賴的話,一個測試失敗會影響其他測試也都失敗。
3. Repeatable – 可重複,要在任何環境都可重複執行。
4. Self-Validating – 顯示驗證結果,可從 report 直接了解失敗原因
5. Timely – 及時,最好是在寫程式之前先寫測試 (TDD 概念)
文章中有提到在考量 Independent 時,會遵循 3A rule:
1. Arrange:建立此測試案例需要的初始值,和思考好的命名和變數命稱來讓測試更容易理解。
2. Act:呼叫目標方法
3. Assert:驗證是否符合預期
最後~
▍回顧本篇 Flask Unittest 文章:
- 環境設置
- Flask 套件選擇和安裝
- 此次架構配置
- 進入主題 Flask 實作單元測試
- 實作步驟一. 配置 main.py
- 實作步驟二. 配置 test 檔
- 實作步驟三. 運行測試
- 補充. 覆蓋率測試
- 補充:單元測試 Unit Test 原則
更多 Flask 教學相關閱讀:
▍關於 Flask 教學系列目錄:
▍關於 Flask 部署相關文章:
- 【Flask 教學系列】實作 GCP 部署 Flask + Nginx + uWSGI
- 【Flask 教學系列】實作 Flask + GitHub Action CI/CD
- 第一集:實作 Dockerfile + flask 教學 (附GitHub完整程式)
- 第二集:實作 Dockerfile + nginx + ssl + flask 教學 (附GitHub完整程式)
- 第三集:實作 Docker-compose (Flask+Nginx+PostgreSQL)
▍其他 Flask 相關教學:
- 【Flask教學系列】Flask 為甚麼需要 WSGI 與 Nginx
- 【Flask教學系列】Flask-SQLAlchemy 資料庫連線&設定入門 (一)
- 【Flask教學系列】Flask-JWT-Extended 實作
- 【Flask教學系列】實作 Flask CORS
- 【Flask教學系列】實作 Flask CSRF Protection
有關 Max行銷誌的最新文章,都會發佈在 Max 的 Facebook 粉絲專頁,如果想看最新更新,還請您按讚或是追蹤唷!
在〈【Flask 教學】實作 Flask 單元測試 Unit Test〉中有 3 則留言
寫得不錯喔
不懂放 drop_all 的意義是想害人嗎?
db.session.remove()
db.drop_all()
嗨嗨,
謝謝留言,已經移除修正!
Best Regards,
Max
留言功能已關閉。