Python知識(shí)分享網(wǎng) - 專(zhuān)業(yè)的Python學(xué)習(xí)網(wǎng)站 學(xué)Python,上Python222
【Python】從同步到異步多核:測(cè)試樁性能優(yōu)化,加速應(yīng)用的開(kāi)發(fā)和驗(yàn)證
匿名網(wǎng)友發(fā)布于:2023-07-18 14:02:15
(侵權(quán)舉報(bào))

測(cè)試工作中常用到的測(cè)試樁mock能力

在我們的測(cè)試工作過(guò)程中,可能會(huì)遇到前端服務(wù)開(kāi)發(fā)完成,依賴(lài)服務(wù)還在開(kāi)發(fā)中;或者我們需要壓測(cè)某個(gè)服務(wù),而這個(gè)服務(wù)的依賴(lài)組件(如測(cè)試環(huán)境MQ) 無(wú)法支撐并發(fā)訪問(wèn)的場(chǎng)景。這個(gè)時(shí)候我們可能就需要一個(gè)服務(wù),來(lái)替代測(cè)試環(huán)境的這些依賴(lài)組件或服務(wù),而這就是本文的主角--測(cè)試樁。

測(cè)試樁可以理解為一個(gè)代理,它可以用于模擬應(yīng)用程序中的外部依賴(lài)項(xiàng),如數(shù)據(jù)庫(kù)、網(wǎng)絡(luò)服務(wù)或其他API,它可以幫助我們?cè)陂_(kāi)發(fā)和測(cè)試過(guò)程中隔離應(yīng)用程序的不同部分,從而使測(cè)試更加可靠和可重復(fù)。

 

應(yīng)用場(chǎng)景

測(cè)試樁使用的一般有以下幾種場(chǎng)景:

【Python】從同步到異步多核:測(cè)試樁性能優(yōu)化,加速應(yīng)用的開(kāi)發(fā)和驗(yàn)證 圖1

本文將選取常用的幾個(gè)場(chǎng)景循序漸進(jìn)地介紹測(cè)試樁的開(kāi)發(fā)和優(yōu)化。

 

簡(jiǎn)單測(cè)試樁

如果在測(cè)試環(huán)境中不方便安裝其他的庫(kù),我們可以使用Python標(biāo)準(zhǔn)庫(kù)中的一個(gè)模塊http.server模塊創(chuàng)建一個(gè)簡(jiǎn)單的HTTP請(qǐng)求測(cè)試樁。

 

# simple_stub.py
# 測(cè)試樁接收GET請(qǐng)求并返回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)行上面的代碼,將看到測(cè)試樁正在監(jiān)聽(tīng)8000端口。您可以使用瀏覽器或curl命令訪問(wèn) http://localhost:8000,將會(huì)收到 {'message': 'Hello, this is a test stub!'}的響應(yīng)。

 

http.server擴(kuò)展:一行命令實(shí)現(xiàn)一個(gè)靜態(tài)文件服務(wù)器

http.server模塊可以作為一個(gè)簡(jiǎn)單的靜態(tài)文件服務(wù)器,用于在本地開(kāi)發(fā)和測(cè)試靜態(tài)網(wǎng)站。要啟動(dòng)靜態(tài)文件服務(wù)器,請(qǐng)?jiān)诿钚兄羞\(yùn)行以下命令:

 

python3 -m http.server [port]

 

其中[port]是可選的端口號(hào),不傳遞時(shí)默認(rèn)為8000。服務(wù)器將在當(dāng)前目錄中提供靜態(tài)文件。

如在日志文件夾中執(zhí)行python -m http.server,就能在web瀏覽器中訪問(wèn)這個(gè)文件夾中的文件和子文件夾的內(nèi)容:

【Python】從同步到異步多核:測(cè)試樁性能優(yōu)化,加速應(yīng)用的開(kāi)發(fā)和驗(yàn)證 圖2

注意: http.server主要用于開(kāi)發(fā)和測(cè)試,性能和安全方面不具備在生產(chǎn)環(huán)境部署的條件

 

性能優(yōu)化:使用異步響應(yīng)

我們?cè)谇懊娴膶?shí)現(xiàn)了一個(gè)簡(jiǎn)單的測(cè)試樁,但在實(shí)際應(yīng)用中,我們可能需要更高的性能和更復(fù)雜的功能。

 
異步響應(yīng)

在只有同樣的資源的情況下,像這樣的有網(wǎng)絡(luò)I/O的服務(wù),使用異步的方式無(wú)疑能更有效地利用系統(tǒng)資源。

說(shuō)到異步的http框架,目前最火熱的當(dāng)然是FastAPI,使用FastAPI實(shí)現(xiàn)上面的功能只需兩步。

首先,安裝FastAPI和Uvicorn:

 

pip install fastapi uvicorn

 

接下來(lái),創(chuàng)建一個(gè)名為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í)行代碼,這個(gè)測(cè)試樁也是監(jiān)聽(tīng)在8000端口。我們可以像之前那樣使用瀏覽器或其他HTTP客戶(hù)端向測(cè)試樁發(fā)起請(qǐng)求。

 

異步編程優(yōu)勢(shì) 介紹

【Python】從同步到異步多核:測(cè)試樁性能優(yōu)化,加速應(yīng)用的開(kāi)發(fā)和驗(yàn)證 圖3

需要注意的是,雖然異步編程在許多場(chǎng)景下可以提供更好的性能,但它并不總是比同步編程快。在某些情況下,如CPU密集型任務(wù),異步編程可能無(wú)法帶來(lái)明顯的性能提升。此外,異步編程通常需要更復(fù)雜的編程模型和錯(cuò)誤處理機(jī)制,因此在選擇異步編程時(shí)需要權(quán)衡其優(yōu)缺點(diǎn)。

 

性能優(yōu)化:利用多核

雖然我們前面使用到了異步的方式來(lái)提升測(cè)試樁的性能,但是代碼還是只是跑在一個(gè)CPU核心上,如果我們要進(jìn)行性能壓測(cè),可能無(wú)法滿(mǎn)足我們的性能需求。這個(gè)時(shí)候我們可以使用 gunicorn庫(kù) 來(lái)利用上服務(wù)器的多核優(yōu)勢(shì)。

 

gunicorn

Gunicorn的主要特點(diǎn)和優(yōu)勢(shì):

【Python】從同步到異步多核:測(cè)試樁性能優(yōu)化,加速應(yīng)用的開(kāi)發(fā)和驗(yàn)證 圖4

 
安裝 gunicorn

 

pip install gunicorn

 

使用 gunicorn 啟動(dòng)服務(wù)

啟動(dòng)服務(wù):

 

gunicorn -w 4  fastapi_stub:app 

 

可以看到,上面的命令啟動(dòng)了4個(gè)worker 進(jìn)程,大家也可以使用ps -ef命令查詢(xún)一下進(jìn)程狀態(tài)。

【Python】從同步到異步多核:測(cè)試樁性能優(yōu)化,加速應(yīng)用的開(kāi)發(fā)和驗(yàn)證 圖5

gunicorn的一些常用參數(shù):

【Python】從同步到異步多核:測(cè)試樁性能優(yōu)化,加速應(yīng)用的開(kāi)發(fā)和驗(yàn)證 圖6

 

Gunicorn提供了許多其他配置選項(xiàng),可以根據(jù)具體需求進(jìn)行調(diào)整。要查看完整的選項(xiàng)列表,可以查看Gunicorn的官方文檔:https://docs.gunicorn.org/en/stable/settings.html。

 

性能優(yōu)化:使用緩存(functools.lru_cache)。

當(dāng)處理重復(fù)的計(jì)算或數(shù)據(jù)檢索任務(wù)時(shí)。使用內(nèi)存緩存(如Python的functools.lru_cache)或外部緩存(如Redis)來(lái)緩存經(jīng)常使用的數(shù)據(jù)也能極大的提升測(cè)試樁的效率。

假設(shè)我們的測(cè)試樁需要使用到計(jì)算計(jì)算斐波那契數(shù)列這樣耗時(shí)的功能,那么緩存結(jié)果,在下次遇到同樣的請(qǐng)求時(shí)直接返回而不是先計(jì)算再返回,將極大的提高資源的使用率、減少響應(yīng)的等待時(shí)間。

如果僅僅是直接返回?cái)?shù)據(jù)的,沒(méi)有進(jìn)行復(fù)雜的計(jì)算的測(cè)試樁,使用lru_cache并沒(méi)有實(shí)際意義。

以下是一個(gè)更合適的使用lru_cache的示例,其中我們將對(duì)斐波那契數(shù)列進(jìn)行計(jì)算并緩存結(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)

 

在這個(gè)示例中,我們使用FastAPI創(chuàng)建了一個(gè)簡(jiǎn)單的HTTP請(qǐng)求測(cè)試樁。我們定義了一個(gè)名為fibonacci的函數(shù),該函數(shù)計(jì)算斐波那契數(shù)列。為了提高性能,我們使用functools.lru_cache對(duì)該函數(shù)進(jìn)行了緩存。

在路由/fibonacci/{n}中,我們調(diào)用fibonacci函數(shù)并返回結(jié)果??梢悦钤L問(wèn) http://localhost:8000/fibonacci/{n}進(jìn)行調(diào)試。

需要注意的是 maxsize參數(shù)是functools.lru_cache裝飾器的一個(gè)配置選項(xiàng),它表示緩存的最大容量。lru_cache使用字典來(lái)存儲(chǔ)緩存項(xiàng),當(dāng)一個(gè)新的結(jié)果需要被緩存時(shí),它會(huì)檢查當(dāng)前緩存的大小。如果緩存已滿(mǎn)(即達(dá)到maxsize),則會(huì)根據(jù)LRU策略移除最近最少使用的緩存項(xiàng)。如果maxsize設(shè)置為None,則緩存可以無(wú)限制地增長(zhǎng),這可能導(dǎo)致內(nèi)存問(wèn)題。

 

 

單元測(cè)試中的mock

 

Python unittest.mock

在Python中,unittest模塊提供了一個(gè)名為unittest.mock的子模塊,用于創(chuàng)建mock對(duì)象。unittest.mock包含一個(gè)名為Mock的類(lèi)以及一個(gè)名為patch的上下文管理器/裝飾器,可以用于替換被測(cè)試代碼中的依賴(lài)項(xiàng)。

 

import requests
from unittest import TestCase
from unittest.mock import patch

# 定義一個(gè)函數(shù) get_user_name,它使用 requests.get 發(fā)起 HTTP 請(qǐng)求以獲取用戶(hù)名稱(chēng)
def get_user_name(user_id):
    response = requests.get(f"https://api.example.com/users/{user_id}")
    return response.json()["name"]

# 創(chuàng)建一個(gè)名為 TestGetUserName 的測(cè)試類(lèi),它繼承自 unittest.TestCase
class TestGetUserName(TestCase):
    # 使用 unittest.mock.patch 裝飾器替換 requests.get 函數(shù)
    @patch("requests.get")
    # 定義一個(gè)名為 test_get_user_name 的測(cè)試方法,它接受一個(gè)名為 mock_get 的參數(shù)
    def test_get_user_name(self, mock_get):
        # 配置 mock_get 的返回值,使其在調(diào)用 json 方法時(shí)返回一個(gè)包含 "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é)

在開(kāi)發(fā)測(cè)試樁時(shí),我們需要根據(jù)實(shí)際需求和后端服務(wù)的特點(diǎn)來(lái)設(shè)計(jì)測(cè)試樁的行為,為的是使其更接近實(shí)際后端服務(wù)的行為,確保測(cè)試結(jié)果具有更高的可靠性和準(zhǔn)確性。

可能還有其他的優(yōu)化方案,歡迎大家提出。希望本文能對(duì)大家的工作帶來(lái)幫助。

轉(zhuǎn)載自:https://www.cnblogs.com/Detector/p/17557317.html