簡體   English   中英

在使用 pytest 進行測試時,如何使用 worker >= 2 干凈地終止 Uvicorn + FastAPI 應用程序

[英]How to terminate a Uvicorn + FastAPI application cleanly with workers >= 2 when testing with pytest

我有一個用 Uvicorn + FastAPI 編寫的應用程序。 我正在使用 PyTest 測試響應時間。

參考How to start a Uvicorn + FastAPI in background when testing with PyTest ,我寫了測試。 但是,當 workers >= 2 時,我發現在完成測試后應用程序還處於活動狀態。

我想在測試結束時干凈地終止應用程序進程。

你有什么主意嗎?

詳情如下所示。

環境

圖書館

  • fastapi == 0.68.0
  • uvicorn == 0.14.0
  • 請求 == 2.26.0
  • pytest == 6.2.4

示例代碼

  • 應用:main.py
     from fastapi import FastAPI app = FastAPI() @app.get("/") def hello_world(): return "hello world"
  • 測試:test_main.py
     from multiprocessing import Process import pytest import requests import time import uvicorn HOST = "127.0.0.1" PORT = 8765 WORKERS = 1 def run_server(host: str, port: int, workers: int, wait: int = 15) -> Process: proc = Process( target=uvicorn.run, args=("main:app",), kwargs={ "host": host, "port": port, "workers": workers, }, ) proc.start() time.sleep(wait) assert proc.is_alive() return proc def shutdown_server(proc: Process): proc.terminate() for _ in range(5): if proc.is_alive(): time.sleep(5) else: return else: raise Exception("Process still alive") def check_response(host: str, port: int): assert requests.get(f"http://{host}:{port}").text == '"hello world"' def check_response_time(host: str, port: int, tol: float = 1e-2): s = time.time() requests.get(f"http://{host}:{port}") e = time.time() assert es < tol @pytest.fixture(scope="session") def server(): proc = run_server(HOST, PORT, WORKERS) try: yield finally: shutdown_server(proc) def test_main(server): check_response(HOST, PORT) check_response_time(HOST, PORT) check_response(HOST, PORT) check_response_time(HOST, PORT)

執行結果

$ curl http://localhost:8765
curl: (7) Failed to connect to localhost port 8765: Connection refused
$ pytest test_main.py
=============== test session starts =============== platform win32 -- Python 3.7.5, pytest-6.2.4, py-1.10.0, pluggy-0.13.1
rootdir: .\
collected 1 item

test_main.py .                                                                                                                                                                                                                         [100%]

=============== 1 passed in 20.23s ===============
$ curl http://localhost:8765
curl: (7) Failed to connect to localhost port 8765: Connection refused
$ sed -i -e "s/WORKERS = 1/WORKERS = 3/g" test_main.py
$ curl http://localhost:8765
curl: (7) Failed to connect to localhost port 8765: Connection refused
$ pytest test_main.py
=============== test session starts =============== platform win32 -- Python 3.7.5, pytest-6.2.4, py-1.10.0, pluggy-0.13.1
rootdir: .\
collected 1 item

test_main.py .                                                                                                                                                                                                                         [100%]

=============== 1 passed in 20.21s ===============
$ curl http://localhost:8765
"hello world"

$ # Why is localhost:8765 still alive?

我自己找到了解決辦法。

謝謝 > https://stackoverflow.com/a/27034438/16567832

解決方案

通過pip install psutil后,更新 test_main.py

from multiprocessing import Process
import psutil
import pytest
import requests
import time
import uvicorn

HOST = "127.0.0.1"
PORT = 8765
WORKERS = 3


def run_server(host: str, port: int, workers: int, wait: int = 15) -> Process:
    proc = Process(
        target=uvicorn.run,
        args=("main:app",),
        kwargs={
            "host": host,
            "port": port,
            "workers": workers,
        },
    )
    proc.start()
    time.sleep(wait)
    assert proc.is_alive()
    return proc


def shutdown_server(proc: Process):

    ##### SOLUTION #####
    pid = proc.pid
    parent = psutil.Process(pid)
    for child in parent.children(recursive=True):
        child.kill()
    ##### SOLUTION END ####

    proc.terminate()
    for _ in range(5):
        if proc.is_alive():
            time.sleep(5)
        else:
            return
    else:
        raise Exception("Process still alive")


def check_response(host: str, port: int):
    assert requests.get(f"http://{host}:{port}").text == '"hello world"'


def check_response_time(host: str, port: int, tol: float = 1e-2):
    s = time.time()
    requests.get(f"http://{host}:{port}")
    e = time.time()
    assert e-s < tol


@pytest.fixture(scope="session")
def server():
    proc = run_server(HOST, PORT, WORKERS)
    try:
        yield
    finally:
        shutdown_server(proc)


def test_main(server):
    check_response(HOST, PORT)
    check_response_time(HOST, PORT)
    check_response(HOST, PORT)
    check_response_time(HOST, PORT)

執行結果

$ curl http://localhost:8765
curl: (7) Failed to connect to localhost port 8765: Connection refused
$ pytest test_main.py
================== test session starts ================== platform win32 -- Python 3.7.5, pytest-6.2.4, py-1.10.0, pluggy-0.13.1
rootdir: .\
collected 1 item

test_main.py .                                                                                                                                                                                                                         [100%]

================== 1 passed in 20.24s ==================
$ curl http://localhost:8765
curl: (7) Failed to connect to localhost port 8765: Connection refused

跟進@hmasdev的回答

import os
import fastapi
import uvicorn
import psutil

    @app.get("/quit")
    def iquit():
        parent_pid = os.getpid()
        parent = psutil.Process(parent_pid)
        for child in parent.children(recursive=True):  # or parent.children() for recursive=False
            child.kill()
        parent.kill()
    
    
    
    
    if __name__ == '__main__':
        uvicorn.run(app, port=36113, host='127.0.0.1')

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM