• 카테고리

    질문 & 답변
  • 세부 분야

    백엔드

  • 해결 여부

    해결됨

테스트를 실행했을 때 RuntimeError: Event loop is closed 에러가 발생합니다.

23.11.26 13:09 작성 조회수 613

3

질문하실 땐 https://gist.github.com/ 를 사용하시면 코드를 쉽게 공유할 수 있습니다!

  • 원하고자 하는 것

  • 실제 작성한 코드

  • 실행한 결과

  • 원하는 결과

     

이렇게 4가지를 꼭 적어주셔야 도와드릴 수 있습니다 :)

 

test_shop_insert_one() 함수를 추가하여 테스트를 실행했을 때 RuntimeError: Event loop is closed 에러가 발생하여 테스트가 실패합니다.

 

app/tests/entities/collections/shop/test_shop_collection.py F                                                                                                [100%]

============================================================================= FAILURES =============================================================================
_______________________________________________________________________ test_shop_insert_one _______________________________________________________________________

    @pytest.mark.asyncio
    async def test_shop_insert_one() -> None:
        # Given
        name = "치킨집"
        category_codes = [CategoryCode.CHICKEN]
        delivery_areas = [
            ShopDeliveryAreaSubDocument(
                poly=GeoJsonPolygon(coordinates=[[[0, 0], [0, 10], [10, 10], [10, 0], [0, 0]]]),
            )
        ]
    
        # When
>       shop = await ShopCollection.insert_one(name, category_codes, delivery_areas)

app/tests/entities/collections/shop/test_shop_collection.py:21: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
app/entities/collections/shop/shop_collection.py:20: in insert_one
    result = await cls._collection.insert_one(
/PATH/OF/POETRY/CACHE/lib/python3.11/site-packages/motor/metaprogramming.py:73: in method
    return framework.run_on_executor(
/PATH/OF/POETRY/CACHE/lib/python3.11/site-packages/motor/frameworks/asyncio/__init__.py:85: in run_on_executor
    return loop.run_in_executor(_EXECUTOR, functools.partial(fn, *args, **kwargs))
/PATH/OF/PYTHON/lib/python3.11/asyncio/base_events.py:816: in run_in_executor
    self._check_closed()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <_UnixSelectorEventLoop running=False closed=True debug=False>

    def _check_closed(self):
        if self._closed:
>           raise RuntimeError('Event loop is closed')
E           RuntimeError: Event loop is closed

/PATH/OF/PYTHON/lib/python3.11/asyncio/base_events.py:519: RuntimeError
===================================================================== short test summary info ======================================================================
FAILED app/tests/entities/collections/shop/test_shop_collection.py::test_shop_insert_one - RuntimeError: Event loop is closed
=================================================================== 1 failed, 1 passed in 0.55s ====================================================================

에러로그는 위와 같습니다.

 

--async-mode 를 test로 변경해보면 저 단계까지 가기 전에 에러가 발생하고

@pytest.mark.asyncio 어노테이션을 추가해줘도 결과는 같습니다.

poetry 를 이용한 환경 구성이 처음이라 해당 지식이 많이 부족하여 이 문제를 어떻게 해결해야하는지 잘 모르겠습니다.

 

파이썬 버전은 3.11.3

poetry 버전은 1.4.2

mongodb 버전은 6.0.11 입니다.

 

[tool.poetry.dependencies]
fastapi = "^0.95.2"
gunicorn = "^20.1.0"
httpx = "^0.24.1"
motor = "^3.1.2"
orjson = "^3.8.14"
python = "^3.11"
uvicorn = "^0.22.0"

[tool.poetry.group.dev.dependencies]
black = {extras = ["d"], version = "^23.3.0"}
coverage = "^7.2.3"
isort = "^5.12.0"
mypy = "^1.3.0"
pytest = "^7.3.1"
pytest-asyncio = "^0.21.0"
toml-sort = "^0.23.0"

 

작성한 코드의 차이점은 db가 로컬에 있지 않고 외부에 있는 관계로

def create_mongo_url(host: str = "localhost", port: int = 27017) -> str:
    return f"mongodb://{host}:{port}"


DATABASE_NAME = os.environ.get("MONGO_DATABASE", "yorigin")
HOST = os.environ.get("MONGO_HOST", "localhost")
PORT = os.environ.get("MONGO_PORT", 27017)

client = AsyncIOMotorClient(create_mongo_url(HOST, int(PORT)))

이와 같은 코드를 추가하여 지정된 host의 mongodb와 통신할 수 있도록 한 것이 전부입니다.

 

확인 부탁드릴 수 있을까요?

답변 3

·

답변을 작성해보세요.

0

ㅎㅎ 다 해결되어서 다행입니다!

어제 버스에서 급하게 내리느라고 이미지만 달랑 올렸었는데 ㅋㅋㅋ

 

요거 미해결로 바뀌어 버리니 답 안주셔도 괜찮습니다!

강의 들으시면서 궁금한 점 있으시면 또 편하게 질문 주셔용

 

파이팅!

 

아 ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ 제가 달아도 미해결로 바뀌어 버리네요 으앜ㅋㅋㅋㅋㅋㅋㅋㅋ

0

혹시 시간이 괜찮으시다면 시간을 따로 잡아서 팀뷰어 원격으로 봐 볼까요? 저도 궁금하네요

다음주 평일 이른 아침, 늦은 저녁 중에 괜찮은 시간 있으세요?

 

예컨데님의 프로필

예컨데

질문자

2023.11.30

https://github.com/Evil-Goblin/delivery_fastapi

코드는 여기 올려두었습니다.

급조한 코드다보니 이쁘지 않아 조금 부끄럽네요.

실행 환경이 사내로컬환경이라서 공유드리기가 어렵습니다.

죄송합니다.

혹시라도 환경정보 확인하고 싶으신 사항 있으시면 말씀 부탁드립니다.

 

저에게 발생한 문제의 경우 단 한번의 성공없이 처음부터 motor의 이벤트루프가 실행되지 않은 것으로 확인되었습니다.

디버그로 따라가봐도 motor 의 find_one 을 수행하려 할 때 이벤트 루프가 닫혀있는 것을 확인할 수 있었습니다.

언급해주신 최초 한번의 테스트는 성공 이후 다른 모든 테스트가 실패한다고 말씀주신 것과 상황이 달라서 저또한 의아했습니다.

결과적으로 해결하긴 했지만 motor 라이브러리를 써본 경험이 없어서 많이 해맸던 것 같습니다.

 

그런데 댓글이 달릴때마다 해결 여부가 미해결로 변경되나보군요.

예컨데님의 프로필

예컨데

질문자

2023.12.01

아하 저게 첫번째 이벤트 루프를 먹어버렸나보군요

중간에 삭제하지 않은 것을 까먹고 삭제한 코드였는데...

 

아아아 다 이해가 된 것 같습니다.

정말 감사합니다.

저도 어서 해당 부분을 테스트해보고 싶네요

다시 한번 정말 감사합니다.

0

예컨데님의 프로필

예컨데

질문자

2023.11.27

해결하였습니다.

motor 라이브러리에서 사용되는 event loop 와 pytest 에서 사용되는 event loop 가 달라서 발생하는 것으로 확인됩니다.

실제 어플리케이션 실행시에 어떻게 동작할 지 까지는 좀 더 확인이 필요하겠지만

db 변수를 lazy loading 을 하도록 변경하고 최초 load 시에 event loop 를 주입받을 수 있도록 하여 fixtures 에서 주입해주는 event loop 를 이용해 motor 클라이언트를 생성하였습니다.

AsyncIOMotorClient(io_loop=event_loop)

이 named parameter 를 이용해 사용할 이벤트루프를 지정해줌에 따라서 테스트가 성공할 수 있었습니다.

이게 pytest_asyncio 의 이벤트루프가 특별한 것인지 motor의 그것이 특별한 것인지는 좀 더 확인이 필요할 것 같습니다.

앗 스스로 해결하셨다니 굉장한데요!

 

현재 강의의 어디 지점이신지 모르겠지만, 제가 알기로

imagescope 가 session 이고, 이름이 event_loop 인 픽스쳐를 만든 후, 이 픽스쳐가 loop 를 yield 하게 만들면 해결이 됩니다!

(섹션 3의 pytest 와 fixture 에서 다룹니다)

 

혹시 위 fixture 가 있는데도 에러가 난 것일까요?

 

P.S.

motor 라이브러리에서 사용되는 event loop 와 pytest 에서 사용되는 event loop 가 달라서 발생하는 것으로 확인됩니다.

라고 해주셨는데요, 아주 중요한 통찰입니다.

 

pytest_asyncio 의 기본 event_loop fixture 는 scope 가 Function 인데요, 때문에 매 번 테스트 함수가 실행될 때 마다 기존 루프가 닫히고 새로운 루프가 생깁니다. (눈물)

따라서 다음과 같은 일이 벌어집니다.

  1. 처음 테스트에서 loop 가 생성.

  2. AsyncIOMotorClient() 가 해당 loop 를 이용해서 정상적으로 쿼리.

  3. 처음 테스트가 성공하고 loop 가 닫힘

  4. loop 가 닫혔다는것을 알 방법이 없는 AsyncIOMotorClient 는 이미 닫힌 루프로 쿼리를 계속 시도

  5. 나머지 테스트는 실패

위와 같은 문제를 해결하기 위해서 전체 테스트를 실행하는 동안 loop 를 하나만 쓰도록 session scope 의 event loop fixture 를 새로 정의하는 것입니다!

 

 

 

 

 

 

 

 

imagefixture 가 있을 때 성공

 

imageimage

fixture 를 주석처리하고 테스트를 실행하면 실패

예컨데님의 프로필

예컨데

질문자

2023.11.27

넵 해당 설정까지 해주었으나 에러가 발생하였습니다.

때문에

image이와 같이 db 초기화를 위해 event loop 를 직접 넣어주고

 

image이와 같이 초기화시 AsyncIOMotorClient 의 io_loop named parameter 에 직접 넣어줌으로서 해결하였습니다.

버전정보 등을 픽스해주신 값들을 그대로 사용했음에도 차이가 생기는게 의아하긴 합니다.

관련해서 좀 더 공부해야할 필요는 있을 것 같습니다.

답변 감사드립니다.