測(cè)試對(duì)于軟件開發(fā)過(guò)程至關(guān)重要,可確保代碼按預(yù)期運(yùn)行且無(wú)缺陷。在Python中,pytest是一種流行的測(cè)試框架,與標(biāo)準(zhǔn)單元測(cè)試模塊相比,它具有多種優(yōu)勢(shì),標(biāo)準(zhǔn)單元測(cè)試模塊是內(nèi)置的Python測(cè)試框架,并且是標(biāo)準(zhǔn)庫(kù)的一部分。 pytest 包括更簡(jiǎn)單的語(yǔ)法、更好的輸出、強(qiáng)大的裝置和豐富的插件生態(tài)系統(tǒng)。本教程將指導(dǎo)您設(shè)置 flask 應(yīng)用程序、集成 pytest 固定裝置以及使用 p
第 1 步 – 設(shè)置環(huán)境
Ubuntu 24.04 默認(rèn)情況下附帶 Python 3。打開終端并運(yùn)行 使用以下命令來(lái)仔細(xì)檢查 Python 3 安裝:
如果 Python 3 已經(jīng)安裝在你的機(jī)器上,上面的命令將 返回 Python 3 安裝的當(dāng)前版本。如果不是 安裝完畢后,您可以運(yùn)行以下命令并獲取Python 3 安裝:
root@ubuntu:~# sudo apt install python3
接下來(lái),您需要在系統(tǒng)上安裝 pip 軟件包安裝程序:
root@ubuntu:~# sudo apt install python3-pip
安裝 pip 后,讓我們安裝 Flask。
第 2 步 – 創(chuàng)建 Flask應(yīng)用程序
讓我們從創(chuàng)建一個(gè)簡(jiǎn)單的 Flask 應(yīng)用程序開始。為您的項(xiàng)目創(chuàng)建一個(gè)新目錄并導(dǎo)航到其中:
root@ubuntu:~# mkdir flask_testing_approot@ubuntu:~# cd flask_testing_app
現(xiàn)在,讓我們創(chuàng)建并激活一個(gè)虛擬環(huán)境來(lái)管理依賴項(xiàng):
root@ubuntu:~# python3 -m venv venvroot@ubuntu:~# source venv/bin/activate
使用以下命令安裝 Flask pip:
root@ubuntu:~# pip install Flask
現(xiàn)在,讓我們創(chuàng)建一個(gè)簡(jiǎn)單的 Flask 應(yīng)用程序。創(chuàng)建一個(gè)名為 app.py 的新文件并添加以下代碼:
from flask import Flask, jsonify app = Flask(__name__)@app.route('/')def home(): return jsonify(message="Hello, Flask!")@app.route('/about')def about(): return jsonify(message="This is the About page")@app.route('/multiply/<int:x>/<int:y>')def multiply(x, y): result = x * y return jsonify(result=result)if __name__ == '__main__': app.run(debug=True)
此應(yīng)用程序有三個(gè)路由:
- /:返回一個(gè)簡(jiǎn)單的“Hello, Flask!”
- /about:返回一個(gè)簡(jiǎn)單的“這是關(guān)于頁(yè)面”消息。
- /multiply/
/ :將兩個(gè)整數(shù)相乘并返回result.
要運(yùn)行應(yīng)用程序,請(qǐng)執(zhí)行以下命令命令:
root@ubuntu:~# flask run
output* Serving Flask app "app" (lazy loading) * Environment: production WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Debug mode: on * Running on http://127.0.0.1:5000/ (Press CTRL C to quit)
從上面的輸出中,您可以注意到服務(wù)器正在 http://127.0.0.1 上運(yùn)行并偵聽端口 5000。打開另一個(gè)Ubuntu控制臺(tái)并一一執(zhí)行以下cURL命令:
- GET:curl http://127.0.0.1:5000/:5000/
- GET:卷曲http://127.0.0.1:5000/about:5000/約
- GET:卷曲http://127.0.0.1:5000/multiply/10/20:5000/乘/10/20
讓我們了解一下這些 GET 請(qǐng)求是什么做:
-
卷曲http://127.0.0.1:5000/::5000/: 這將向 Flask 應(yīng)用程序的根路由(‘/’)發(fā)送 GET 請(qǐng)求。服務(wù)器響應(yīng)一個(gè)包含消息“Hello, Flask!”的 JSON 對(duì)象,演示了我們的主路由的基本功能。
-
curl http://127.0.0.1:5000/about::5000/about: 這將向 /about 路由發(fā)送 GET 請(qǐng)求。服務(wù)器使用包含消息“這是關(guān)于頁(yè)面”的 JSON 對(duì)象進(jìn)行響應(yīng)。這表明我們的路線運(yùn)行正常。
-
curl http://127.0.0.1:5000/multiply/10/20::5000/multiply/10/20: 這會(huì)向 /multiply 路由發(fā)送一個(gè) GET 請(qǐng)求,其中包含兩個(gè)參數(shù):10 和 20。服務(wù)器將這些參數(shù)相乘 數(shù)字并以包含結(jié)果 (200) 的 JSON 對(duì)象進(jìn)行響應(yīng)。 這說(shuō)明我們的multiply路由可以正確處理URL 參數(shù)并執(zhí)行計(jì)算。
這些 GET 請(qǐng)求允許我們與 Flask 交互 應(yīng)用程序的 API 端點(diǎn),檢索信息或觸發(fā) 在服務(wù)器上執(zhí)行操作而不修改任何數(shù)據(jù)。它們對(duì)于 獲取數(shù)據(jù)、測(cè)試端點(diǎn)功能并驗(yàn)證我們的 路由按預(yù)期響應(yīng)。
讓我們看看其中的每個(gè) GET 請(qǐng)求操作:
root@ubuntu:~# curl root@ubuntu:~# curl http://127.0.0.1:5000/:5000/
Output{"message":"Hello, Flask!"}
root@ubuntu: ?#卷曲root@ubuntu:~# curl http://127.0.0.1:5000/about:500 0/關(guān)于
Output{"message":"This is the About page"}
root@ubuntu:~# curl root@ubuntu:~# curl http://127.0.0.1:5000/multiply/10/20:5000/multiply/10/20
Output{"result":200}
步驟3 – 安裝 pytest 并編寫您的第一個(gè)測(cè)試
現(xiàn)在您已經(jīng)有了一個(gè)基本的 Flask 應(yīng)用程序,讓我們安裝 pytest 并編寫一些單元測(cè)試。
使用 pip 安裝 pytest:
root@ubuntu:~# pip install pytest
創(chuàng)建一個(gè)測(cè)試目錄來(lái)存儲(chǔ)您的測(cè)試files:
root@ubuntu:~# mkdir tests
現(xiàn)在,讓我們創(chuàng)建一個(gè)名為 test_app.py 的新文件并添加以下代碼:
# Import sys module for modifying Python's runtime environmentimport sys# Import os module for interacting with the operating systemimport os# Add the parent directory to sys.pathsys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))# Import the Flask app instance from the main app filefrom app import app # Import pytest for writing and running testsimport [email protected] client(): """A test client for the app.""" with app.test_client() as client: yield clientdef test_home(client): """Test the home route.""" response = client.get('/') assert response.status_code == 200 assert response.json == {"message": "Hello, Flask!"}def test_about(client): """Test the about route.""" response = client.get('/about') assert response.status_code == 200 assert response.json == {"message": "This is the About page"}def test_multiply(client): """Test the multiply route with valid input.""" response = client.get('/multiply/3/4') assert response.status_code == 200 assert response.json == {"result": 12}def test_multiply_invalid_input(client): """Test the multiply route with invalid input.""" response = client.get('/multiply/three/four') assert response.status_code == 404def test_non_existent_route(client): """Test for a non-existent route.""" response = client.get('/non-existent') assert response.status_code == 404
我們來(lái)分解一下這個(gè)測(cè)試中的功能文件:
-
@pytest.fixture def client(): 這是一個(gè) pytest 夾具,為我們的 Flask 應(yīng)用程序創(chuàng)建一個(gè)測(cè)試客戶端。它使用 app.test_client() 方法創(chuàng)建一個(gè)客戶端,該客戶端可以向我們的應(yīng)用程序發(fā)送請(qǐng)求,而無(wú)需運(yùn)行實(shí)際的服務(wù)器。 Yield 語(yǔ)句允許客戶端在測(cè)試中使用,然后在每次測(cè)試后正確關(guān)閉。
-
def test_home(client): 此函數(shù)測(cè)試我們應(yīng)用程序的主路由 (/)。它發(fā)送 使用測(cè)試客戶端向路由發(fā)出 GET 請(qǐng)求,然后斷言 響應(yīng)狀態(tài)代碼為 200(正常)并且 JSON 響應(yīng)與 預(yù)期消息。
-
def test_about(client): 與test_home類似,該函數(shù)測(cè)試about路由(/about)。它檢查 200 狀態(tài)代碼并驗(yàn)證 JSON 響應(yīng)內(nèi)容。
-
def test_multiply(client): 此函數(shù)使用有效輸入 (/multiply/3/4) 測(cè)試乘法路由。它檢查狀態(tài)代碼是否為 200 并且 JSON 響應(yīng)是否包含正確的乘法結(jié)果。
-
def test_multiply_invalid_input(client): 此函數(shù)測(cè)試具有無(wú)效輸入的乘法路徑(乘法/三/四)。 它檢查狀態(tài)代碼是否為 404(未找到),這是 當(dāng)路由無(wú)法匹配字符串輸入時(shí)的預(yù)期行為 必需的整數(shù)參數(shù)。
-
def test_non_existent_route(client): 該函數(shù)測(cè)試當(dāng)訪問(wèn)不存在的路由時(shí)應(yīng)用程序的行為。它向 /non-existent 發(fā)送 GET 請(qǐng)求, 我們的 Flask 應(yīng)用程序中沒(méi)有定義它。測(cè)試斷言 響應(yīng)狀態(tài)代碼為 404(未找到),確保我們的應(yīng)用程序正確 處理對(duì)未定義路由的請(qǐng)求。
這些測(cè)試涵蓋了 Flask 應(yīng)用程序的基本功能,確保 每條路線都能正確響應(yīng)有效輸入,并且乘法 路由適當(dāng)?shù)靥幚頍o(wú)效輸入。通過(guò)使用 pytest,我們可以輕松運(yùn)行這些測(cè)試來(lái)驗(yàn)證我們的應(yīng)用程序是否按預(yù)期工作。
第 4 步 – 運(yùn)行測(cè)試
要運(yùn)行測(cè)試,請(qǐng)執(zhí)行以下命令:
root@ubuntu:~# pytest
默認(rèn)情況下,pytest發(fā)現(xiàn)過(guò)程將遞歸掃描當(dāng)前文件夾及其子文件夾對(duì)于名稱以“test_”開頭或以“_test”結(jié)尾的文件。然后執(zhí)行位于這些文件中的測(cè)試。您應(yīng)該看到類似以下內(nèi)容的輸出:
Outputplatform Linux -- Python 3.12.3, pytest-8.3.2, pluggy-1.5.0 rootdir: /home/user/flask_testing_app collected 5 items tests/test_app.py .... [100%]======================================================= 5 passed in 0.19s ========================================================
這表明所有測(cè)試均已成功通過(guò)。
第 5 步:在 pytest 中使用 Fixtures
夾具是用于提供數(shù)據(jù)或資源的函數(shù) 測(cè)試。它們可用于設(shè)置和拆除測(cè)試環(huán)境、加載 數(shù)據(jù),或執(zhí)行其他設(shè)置任務(wù)。在 pytest 中,裝置是使用 @pytest.fixture 裝飾器定義的。
以下是如何增強(qiáng)現(xiàn)有裝置。更新客戶端固定裝置以使用安裝和拆卸邏輯:
@pytest.fixturedef client(): """Set up a test client for the app with setup and teardown logic.""" print("nSetting up the test client") with app.test_client() as client: yield client # This is where the testing happens print("Tearing down the test client")def test_home(client): """Test the home route.""" response = client.get('/') assert response.status_code == 200 assert response.json == {"message": "Hello, Flask!"}def test_about(client): """Test the about route.""" response = client.get('/about') assert response.status_code == 200 assert response.json == {"message": "This is the About page"}def test_multiply(client): """Test the multiply route with valid input.""" response = client.get('/multiply/3/4') assert response.status_code == 200 assert response.json == {"result": 12}def test_multiply_invalid_input(client): """Test the multiply route with invalid input.""" response = client.get('/multiply/three/four') assert response.status_code == 404def test_non_existent_route(client): """Test for a non-existent route.""" response = client.get('/non-existent') assert response.status_code == 404
這個(gè) setup 添加了打印語(yǔ)句來(lái)演示安裝和拆卸 測(cè)試輸出中的階段。這些可以替換為實(shí)際資源 如果需要的話,管理代碼。
讓我們嘗試再次運(yùn)行測(cè)試:
root@ubuntu:~# pytest -vs
-v 標(biāo)志增加了詳細(xì)程度,而 -s 標(biāo)志允許打印語(yǔ)句將顯示在控制臺(tái)輸出中。
您應(yīng)該看到以下內(nèi)容輸出:
Outputplatform linux -- Python 3.12.3, pytest-8.3.2, pluggy-1.5.0 rootdir: /home/user/flask_testing_app cachedir: .pytest_cache collected 5 items tests/test_app.py::test_home Setting up the test client PASSED Tearing down the test client tests/test_app.py::test_about Setting up the test client PASSED Tearing down the test client tests/test_app.py::test_multiply Setting up the test client PASSED Tearing down the test client tests/test_app.py::test_multiply_invalid_input Setting up the test client PASSED Tearing down the test client tests/test_app.py::test_non_existent_route Setting up the test client PASSED Tearing down the test client============================================ 5 passed in 0.35s =============================================
第 6 步:添加失敗測(cè)試用例
讓我們向現(xiàn)有測(cè)試文件添加一個(gè)失敗測(cè)試用例。修改 test_app.py 文件并在失敗的測(cè)試用例末尾添加以下函數(shù)以獲得不正確的結(jié)果:
def test_multiply_edge_cases(client): """Test the multiply route with edge cases to demonstrate failing tests.""" # Test with zero response = client.get('/multiply/0/5') assert response.status_code == 200 assert response.json == {"result": 0} # Test with large numbers (this might fail if not handled properly) response = client.get('/multiply/1000000/1000000') assert response.status_code == 200 assert response.json == {"result": 1000000000000} # Intentional failing test: incorrect result response = client.get('/multiply/2/3') assert response.status_code == 200 assert response.json == {"result": 7}, "This test should fail to demonstrate a failing case"
讓我們休息一下分析 test_multiply_edge_cases 函數(shù)并解釋每個(gè)部分的含義執(zhí)行:
-
使用零進(jìn)行測(cè)試:此測(cè)試檢查乘法函數(shù)是否正確處理 乘以零。我們期望相乘時(shí)結(jié)果為0 任意數(shù)為零。這是一個(gè)需要測(cè)試的重要邊緣情況,因?yàn)橐恍? 實(shí)現(xiàn)可能存在零乘法問(wèn)題。
-
大數(shù)測(cè)試:此測(cè)試驗(yàn)證乘法函數(shù)是否可以處理大數(shù) 沒(méi)有溢出或精度問(wèn)題。我們乘以二百萬(wàn) 值,預(yù)計(jì)結(jié)果為一萬(wàn)億。這項(xiàng)測(cè)試至關(guān)重要,因?yàn)? 它檢查函數(shù)能力的上限。請(qǐng)注意,這 如果服務(wù)器的實(shí)現(xiàn)不能處理大量數(shù)據(jù),則可能會(huì)失敗 正確地,這可能表明需要大量的庫(kù)或 不同的數(shù)據(jù)類型。
-
故意失敗測(cè)試:此測(cè)試被故意設(shè)置為失敗。它檢查 2 * 3 是否等于 7, 這是不正確的。該測(cè)試旨在演示失敗的測(cè)試如何 查看測(cè)試輸出。這有助于理解如何識(shí)別 并調(diào)試失敗的測(cè)試,這是測(cè)試驅(qū)動(dòng)的一項(xiàng)基本技能 開發(fā)和調(diào)試過(guò)程。
通過(guò)包含這些邊緣情況和故意失敗,您可以 不僅測(cè)試多重路由的基本功能,還測(cè)試 極端條件下的行為及其錯(cuò)誤報(bào)告 能力。這種測(cè)試方法有助于確保穩(wěn)健性和 我們應(yīng)用程序的可靠性。
讓我們嘗試再次運(yùn)行測(cè)試:
root@ubuntu:~# pytest -vs
您應(yīng)該看到以下輸出:
Outputplatform linux -- Python 3.12.3, pytest-8.3.2, pluggy-1.5.0 rootdir: /home/user/flask_testing_app cachedir: .pytest_cache collected 6 items tests/test_app.py::test_home Setting up the test client PASSED Tearing down the test client tests/test_app.py::test_about Setting up the test client PASSED Tearing down the test client tests/test_app.py::test_multiply Setting up the test client PASSED Tearing down the test client tests/test_app.py::test_multiply_invalid_input Setting up the test client PASSED Tearing down the test client tests/test_app.py::test_non_existent_route Setting up the test client PASSED Tearing down the test client tests/test_app.py::test_multiply_edge_cases Setting up the test client FAILED Tearing down the test client================================================================= FAILURES ==================================================================_________________________________________________________ test_multiply_edge_cases __________________________________________________________ client = <FlaskClient <Flask 'app'>> def test_multiply_edge_cases(client): """Test the multiply route with edge cases to demonstrate failing tests.""" # Test with zero response = client.get('/multiply/0/5') assert response.status_code == 200 assert response.json == {"result": 0} # Test with large numbers (this might fail if not handled properly) response = client.get('/multiply/1000000/1000000') assert response.status_code == 200 assert response.json == {"result": 1000000000000} # Intentional failing test: incorrect result response = client.get('/multiply/2/3') assert response.status_code == 200> assert response.json == {"result": 7}, "This test should fail to demonstrate a failing case"E AssertionError: This test should fail to demonstrate a failing caseE assert {'result': 6} == {'result': 7}E E Differing items: E {'result': 6} != {'result': 7}E E Full diff: E {E - 'result': 7,... E E ...Full output truncated (4 lines hidden), use '-vv' to show tests/test_app.py:61: AssertionError========================================================== short test summary info ==========================================================FAILED tests/test_app.py::test_multiply_edge_cases - AssertionError: This test should fail to demonstrate a failing case======================================================== 1 failed, 5 passed in 0.32s ========================================================
上面的失敗消息表明測(cè)試 test_multiply_edge_cases 中測(cè)試/test_app.py 文件失敗。具體來(lái)說(shuō),此測(cè)試函數(shù)中的最后一個(gè)斷言導(dǎo)致了失敗。
這種故意失敗對(duì)于演示如何測(cè)試非常有用 報(bào)告故障以及故障中提供了哪些信息 信息。它顯示了發(fā)生故障的確切行, 預(yù)期值和實(shí)際值,以及兩者之間的差異。
在現(xiàn)實(shí)場(chǎng)景中,您將修復(fù)代碼以進(jìn)行測(cè)試 如果預(yù)期結(jié)果不正確,則通過(guò)或調(diào)整測(cè)試。然而, 在這種情況下,失敗是出于教育目的而故意發(fā)生的。