目錄
測試工作中常用到的測試樁mock能力
應(yīng)用場景
簡單測試樁
http.server擴(kuò)展:一行命令實(shí)現(xiàn)一個靜態(tài)文件服務(wù)器
性能優(yōu)化:使用異步響應(yīng)
異步響應(yīng)
能優(yōu)化:利用多核
gunicorn
安裝 gunicorn
使用 gunicorn 啟動服務(wù)
性能優(yōu)化:使用緩存(functools.lru_cache)。
單元測試中的mock
Python unittest.mock
總結(jié)
資料獲取方法
測試工作中常用到的測試樁mock能力
在我們的測試工作過程中,可能會遇到前端服務(wù)開發(fā)完成,依賴服務(wù)還在開發(fā)中;或者我們需要壓測某個服務(wù),而這個服務(wù)的依賴組件(如測試環(huán)境MQ
) 無法支撐并發(fā)訪問的場景。這個時候我們可能就需要一個服務(wù),來替代測試環(huán)境的這些依賴組件或服務(wù),而這就是本文的主角--測試樁。
測試樁可以理解為一個代理,它可以用于模擬應(yīng)用程序中的外部依賴項,如數(shù)據(jù)庫、網(wǎng)絡(luò)服務(wù)或其他API,它可以幫助我們在開發(fā)和測試過程中隔離應(yīng)用程序的不同部分,從而使測試更加可靠和可重復(fù)。
應(yīng)用場景
測試樁使用的一般有以下幾種場景:
場景 | 使用測試樁的原因與目的 |
---|---|
單元測試 | 隔離被測代碼與其他組件或外部依賴的交互,便于在不考慮其他部分的情況下對被測代碼進(jìn)行測試。 |
集成測試 | 當(dāng)某些組件未實(shí)現(xiàn)或不可用時,使用測試樁模擬這些組件,以便繼續(xù)進(jìn)行集成測試。 |
性能測試 | 快速生成高負(fù)載和大量并發(fā)請求,評估系統(tǒng)在高負(fù)載條件下的性能表現(xiàn)。 |
故障注入和恢復(fù)測試 | 模擬故障(如網(wǎng)絡(luò)故障、服務(wù)宕機(jī)等),驗(yàn)證系統(tǒng)在遇到故障時的行為和恢復(fù)能力。 |
API測試 | 使用測試樁模擬API的響應(yīng),以便在API實(shí)現(xiàn)完成之前就可以進(jìn)行客戶端開發(fā)和測試。 |
第三方服務(wù)測試 | 在開發(fā)和測試階段避免與真實(shí)的第三方服務(wù)進(jìn)行交互,降低額外成本和不穩(wěn)定的測試結(jié)果。測試樁用于模擬這些第三方服務(wù),使得在不影響真實(shí)服務(wù)的情況下進(jìn)行測試。 |
本文將選取常用的幾個場景循序漸進(jìn)地介紹測試樁的開發(fā)和優(yōu)化。
簡單測試樁
如果在測試環(huán)境中不方便安裝其他的庫,我們可以使用Python標(biāo)準(zhǔn)庫中的一個模塊http.server
模塊創(chuàng)建一個簡單的HTTP請求測試樁。
# simple_stub.py
# 測試樁接收GET請求并返回JSON數(shù)據(jù)。
import json
from http.server import BaseHTTPRequestHandler, HTTPServer
class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
def do_GET(self):
content = json.dumps({"message": "Hello, this is a test stub!"}).encode("utf-8")
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", f"{len(content)}")
self.end_headers()
self.wfile.write(content)
if __name__ == "__main__":
server_address = ("", 8000)
httpd = HTTPServer(server_address, SimpleHTTPRequestHandler)
print("Test stub is running on port 8000")
httpd.serve_forever()
運(yùn)行上面的代碼,將看到測試樁正在監(jiān)聽8000端口。您可以使用瀏覽器或curl
命令訪問?http://localhost:8000
,將會收到?{'message': 'Hello, this is a test stub!'}
的響應(yīng)。
http.server
擴(kuò)展:一行命令實(shí)現(xiàn)一個靜態(tài)文件服務(wù)器
http.server
模塊可以作為一個簡單的靜態(tài)文件服務(wù)器,用于在本地開發(fā)和測試靜態(tài)網(wǎng)站。要啟動靜態(tài)文件服務(wù)器,請在命令行中運(yùn)行以下命令:
python3 -m http.server [port]
其中[port]是可選的端口號,不傳遞時默認(rèn)為8000。服務(wù)器將在當(dāng)前目錄中提供靜態(tài)文件。
如在日志文件夾中執(zhí)行python -m http.server
,就能在web瀏覽器中訪問這個文件夾中的文件和子文件夾的內(nèi)容:
注意:?
http.server
主要用于開發(fā)和測試,性能和安全方面不具備在生產(chǎn)環(huán)境部署的條件
性能優(yōu)化:使用異步響應(yīng)
我們在前面的實(shí)現(xiàn)了一個簡單的測試樁,但在實(shí)際應(yīng)用中,我們可能需要更高的性能和更復(fù)雜的功能。
異步響應(yīng)
在只有同樣的資源的情況下,像這樣的有網(wǎng)絡(luò)I/O的服務(wù),使用異步的方式無疑能更有效地利用系統(tǒng)資源。
說到異步的http框架,目前最火熱的當(dāng)然是FastAPI
,使用FastAPI
實(shí)現(xiàn)上面的功能只需兩步。
首先,安裝FastAPI和Uvicorn:
pip install fastapi uvicorn
接下來,創(chuàng)建一個名為fastapi_stub.py
的文件,其中包含以下內(nèi)容:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def get_request():
return {"message": "Hello, this is an optimized test stub!"}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
執(zhí)行代碼,這個測試樁也是監(jiān)聽在8000端口。我們可以像之前那樣使用瀏覽器或其他HTTP客戶端向測試樁發(fā)起請求。
點(diǎn)擊查看異步編程優(yōu)勢 介紹
能優(yōu)化:利用多核
雖然我們前面使用到了異步的方式來提升測試樁的性能,但是代碼還是只是跑在一個CPU核心上,如果我們要進(jìn)行性能壓測,可能無法滿足我們的性能需求。這個時候我們可以使用?gunicorn
庫 來利用上服務(wù)器的多核優(yōu)勢。
gunicorn
Gunicorn的主要特點(diǎn)和優(yōu)勢:
特點(diǎn)與優(yōu)勢 | 說明 |
---|---|
簡單易用 | Gunicorn易于安裝和配置,可以與許多Python Web框架(如Flask、Django、FastAPI等)無縫集成。 |
多進(jìn)程 | Gunicorn使用預(yù)先分叉的工作模式,創(chuàng)建多個子進(jìn)程處理并發(fā)請求。這有助于提高應(yīng)用程序的性能和響應(yīng)能力。 |
兼容性 | Gunicorn遵循WSGI規(guī)范,這意味著它可以與遵循WSGI規(guī)范的任何Python Web應(yīng)用程序一起使用。 |
可配置性 | Gunicorn提供了許多配置選項,如工作進(jìn)程數(shù)量、工作進(jìn)程類型(同步、異步)、超時設(shè)置等。這使得Gunicorn可以根據(jù)具體需求進(jìn)行靈活配置。 |
部署友好 | Gunicorn在生產(chǎn)環(huán)境中非常受歡迎,因?yàn)樗喕瞬渴鹆鞒?。Gunicorn可以與其他工具(如Nginx、Supervisor等)一起使用,以便更好地管理和擴(kuò)展Web應(yīng)用程序。 |
安裝 gunicorn
pip install gunicorn
使用 gunicorn 啟動服務(wù)
啟動服務(wù):
gunicorn -w 4 fastapi_stub:app
可以看到,上面的命令啟動了4個worker 進(jìn)程,大家也可以使用ps -ef
命令查詢一下進(jìn)程狀態(tài)。
gunicorn的一些常用參數(shù):
參數(shù) | 說明 |
---|---|
-w, --workers | 設(shè)置工作進(jìn)程的數(shù)量。根據(jù)系統(tǒng)的CPU核心數(shù)和應(yīng)用程序的負(fù)載特征來調(diào)整。默認(rèn)值為1。 |
-k, --worker-class | 設(shè)置工作進(jìn)程的類型??梢允?code>sync(默認(rèn))、gevent 、eventlet 等。如果使用異步工作進(jìn)程,需要安裝相應(yīng)的庫。例如,對于FastAPI應(yīng)用程序,可以使用-k uvicorn.workers.UvicornWorker 。 |
-b, --bind | 設(shè)置服務(wù)器綁定的地址和端口。格式為address:port 。例如:-b 0.0.0.0:8000 。默認(rèn)值為127.0.0.1:8000 。 |
--timeout | 設(shè)置工作進(jìn)程的超時時間(以秒為單位)。如果工作進(jìn)程在指定的時間內(nèi)沒有完成任務(wù),它將被重啟。默認(rèn)值為30秒。 |
--log-level | 設(shè)置日志級別??梢允?code>debug、info 、warning 、error 或critical 。默認(rèn)值為info 。 |
--access-logfile | 設(shè)置訪問日志文件的路徑。默認(rèn)情況下,訪問日志將輸出到標(biāo)準(zhǔn)錯誤流。要禁用訪問日志,請使用- 。例如:--access-logfile - 。 |
--error-logfile | 設(shè)置錯誤日志文件的路徑。默認(rèn)情況下,錯誤日志將輸出到標(biāo)準(zhǔn)錯誤流。要禁用錯誤日志,請使用- 。例如:--error-logfile - 。 |
--reload | 在開發(fā)環(huán)境中使用此選項,當(dāng)應(yīng)用程序代碼發(fā)生更改時,Gunicorn將自動重新加載。不建議在生產(chǎn)環(huán)境中使用。 |
--daemon | 使用此選項以守護(hù)進(jìn)程模式運(yùn)行Gunicorn。在這種模式下,Gunicorn將在后臺運(yùn)行,并在啟動時自動分離。 |
Gunicorn提供了許多其他配置選項,可以根據(jù)具體需求進(jìn)行調(diào)整。要查看完整的選項列表,可以查看Gunicorn的官方文檔:https://docs.gunicorn.org/en/stable/settings.html。
性能優(yōu)化:使用緩存(functools.lru_cache)。
當(dāng)處理重復(fù)的計算或數(shù)據(jù)檢索任務(wù)時。使用內(nèi)存緩存(如Python的functools.lru_cache)或外部緩存(如Redis)來緩存經(jīng)常使用的數(shù)據(jù)也能極大的提升測試樁的效率。
假設(shè)我們的測試樁需要使用到計算計算斐波那契數(shù)列這樣耗時的功能,那么緩存結(jié)果,在下次遇到同樣的請求時直接返回而不是先計算再返回,將極大的提高資源的使用率、減少響應(yīng)的等待時間。
如果僅僅是直接返回數(shù)據(jù)的,沒有進(jìn)行復(fù)雜的計算的測試樁,使用
lru_cache
并沒有實(shí)際意義。
以下是一個更合適的使用lru_cache
的示例,其中我們將對斐波那契數(shù)列進(jìn)行計算并緩存結(jié)果:
from fastapi import FastAPI
from functools import lru_cache
app = FastAPI()
@lru_cache(maxsize=100)
def fibonacci(n: int):
if n <= 1:
return n
else:
return fibonacci(n - 1) + fibonacci(n - 2)
@app.get("/fibonacci/{n}")
async def get_fibonacci(n: int):
result = fibonacci(n)
return {"result": result}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
在這個示例中,我們使用FastAPI創(chuàng)建了一個簡單的HTTP請求測試樁。我們定義了一個名為fibonacci
的函數(shù),該函數(shù)計算斐波那契數(shù)列。為了提高性能,我們使用functools.lru_cache
對該函數(shù)進(jìn)行了緩存。
在路由/fibonacci/{n}
中,我們調(diào)用fibonacci
函數(shù)并返回結(jié)果。可以命令訪問?http://localhost:8000/fibonacci/{n}
進(jìn)行調(diào)試。
需要注意的是?maxsize
參數(shù)是functools.lru_cache
裝飾器的一個配置選項,它表示緩存的最大容量。lru_cache
使用字典來存儲緩存項,當(dāng)一個新的結(jié)果需要被緩存時,它會檢查當(dāng)前緩存的大小。如果緩存已滿(即達(dá)到maxsize
),則會根據(jù)LRU策略移除最近最少使用的緩存項。如果maxsize
設(shè)置為None
,則緩存可以無限制地增長,這可能導(dǎo)致內(nèi)存問題。
單元測試中的mock
Python unittest.mock
在Python中,unittest模塊提供了一個名為unittest.mock的子模塊,用于創(chuàng)建mock對象。unittest.mock包含一個名為Mock的類以及一個名為patch的上下文管理器/裝飾器,可以用于替換被測試代碼中的依賴項。
import requests
from unittest import TestCase
from unittest.mock import patch
# 定義一個函數(shù) get_user_name,它使用 requests.get 發(fā)起 HTTP 請求以獲取用戶名稱
def get_user_name(user_id):
response = requests.get(f"https://api.example.com/users/{user_id}")
return response.json()["name"]
# 創(chuàng)建一個名為 TestGetUserName 的測試類,它繼承自 unittest.TestCase
class TestGetUserName(TestCase):
# 使用 unittest.mock.patch 裝飾器替換 requests.get 函數(shù)
@patch("requests.get")
# 定義一個名為 test_get_user_name 的測試方法,它接受一個名為 mock_get 的參數(shù)
def test_get_user_name(self, mock_get):
# 配置 mock_get 的返回值,使其在調(diào)用 json 方法時返回一個包含 "name": "Alice" 的字典
mock_get.return_value.json.return_value = {"name": "Alice"}
# 調(diào)用 get_user_name 函數(shù),并傳入 user_id 參數(shù)
user_name = get_user_name(1)
# 使用 unittest.TestCase 的 assertEqual 方法檢查 get_user_name 的返回值是否等于 "Alice"
self.assertEqual(user_name, "Alice")
# 使用 unittest.mock.Mock 的 assert_called_with 方法檢查 mock_get 是否被正確調(diào)用
mock_get.assert_called_with("https://api.example.com/users/1")
總結(jié)
在開發(fā)測試樁時,我們需要根據(jù)實(shí)際需求和后端服務(wù)的特點(diǎn)來設(shè)計測試樁的行為,為的是使其更接近實(shí)際后端服務(wù)的行為,確保測試結(jié)果具有更高的可靠性和準(zhǔn)確性。
可能還有其他的優(yōu)化方案,歡迎大家提出。希望本文能對大家的工作帶來幫助。
如果覺得還不錯,就在右下角點(diǎn)個贊吧,感謝!
資料獲取方法
【留言777】
各位想獲取源碼等教程資料的朋友請點(diǎn)贊 + 評論 + 收藏,三連!文章來源:http://www.zghlxwxcb.cn/news/detail-623330.html
三連之后我會在評論區(qū)挨個私信發(fā)給你們~文章來源地址http://www.zghlxwxcb.cn/news/detail-623330.html
到了這里,關(guān)于【Python】從同步到異步多核:測試樁性能優(yōu)化,加速應(yīng)用的開發(fā)和驗(yàn)證的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!