pytest로 비동기(asyncio) 코드 테스트하기
pytest로 비동기(asyncio) 코드 테스트하기
파이썬에서 async def 함수 안의 await 코드를 테스트하려면, 평범한 pytest만으로는 실행 루프(이벤트 루프)가 없어서 어려워요. pytest-asyncio는 pytest 플러그인으로, 코루틴을 테스트 함수로 직접 받아서 await 코드를 테스트 안에서 실행할 수 있게 해줘요. 여기서는 설치와 기본 사용법, 그리고 테스트 발견 모드(엄격·자동)와 이벤트 루프가 어떻게 배정되는지까지 살펴볼게요.
pytest-asyncio란 무엇이고 어떻게 쓰나요
pytest-asyncio는 pytest 플러그인이에요. asyncio 라이브러리를 쓰는 코드를 테스트하는 걸 도와주고, 구체적으로 코루틴을 테스트 함수로 지원해요. 덕분에 테스트 안에서 await 코드를 실행할 수 있죠. 예를 들어 아래 코드는 pytest가 테스트 아이템으로 실행해요.
@pytest.mark.asyncio
async def test_some_asyncio_code():
res = await library.do_something()
assert b"expected result" == res
설치는 다른 pytest 플러그인처럼 pip로 하면 돼요.
pip install pytest-asyncio
한 가지 주의할 점은, 표준 라이브러리의 unittest를 상속한 테스트 클래스는 지원하지 않는다는 거예요. 그런 경우에는 unittest.IsolatedAsyncioTestCase나 asynctest 같은 비동기 프레임워크를 쓰는 게 좋아요.
발견 모드: strict와 auto
pytest-asyncio는 테스트 발견 방식에 두 가지 모드를 제공해요. --asyncio-mode 커맨드라인 플래그나 설정 파일로 정할 수 있어요.
strict 모드 (기본값)에서는 asyncio 마커가 붙은 테스트만 실행되고, @pytest_asyncio.fixture로 장식된 비동기 픽스처만 평가돼요. 마커·데코레이터가 없는 테스트 함수와 픽스처는 pytest-asyncio가 다루지 않아요. 여러 비동기 라이브러리(asyncio, trio 등)를 한 코드베이스에서 같이 쓰려는 프로젝트에 적합해요. pytest는 설치된 플러그인을 자동으로 활성화하므로 플러그인들이 기본 설정에서 평화롭게 공존해야 하기 때문에, strict 모드가 기본값이에요.
[tool.pytest.ini_options]
asyncio_mode = "strict"
auto 모드에서는 pytest-asyncio가 모든 비동기 테스트 함수에 자동으로 asyncio 마커를 붙여요. 또 @pytest.fixture든 @pytest_asyncio.fixture든 모든 비동기 픽스처를 자동으로 관리해요. asyncio만 쓰는 프로젝트에서 가장 단순한 설정을 원한다면 이 모드가 권장돼요. 반대로 asyncio와 trio 같은 여러 비동기 라이브러리를 지원하려면 strict 모드가 낫습니다.
[tool.pytest.ini_options]
asyncio_mode = "auto"
이벤트 루프 배정 방식
pytest-asyncio는 pytest 수집기의 각 계층마다 하나의 asyncio 이벤트 루프를 제공해요. 기본적으로 각 테스트는 Function 수집기가 제공하는 루프(가장 좁은 범위)에서 실행돼요. 그래서 테스트 간 격리 수준이 가장 높아요.
두 개 이상의 테스트가 공통 조상 수집기를 공유한다면, asyncio 마커에 loop_scope 인자를 넘겨 그 조상의 루프에서 실행되게 할 수 있어요. 이웃한 테스트는 같은 이벤트 루프 범위를 쓰는 게 권장돼요. 예를 들어 한 클래스·모듈의 테스트는 모두 같은 scope를 쓰는 게 좋아요. 서로 다른 scope를 섞어 쓰면 테스트 코드 이해가 어려워질 수 있거든요.
실행 순서: 순차 실행
pytest-asyncio는 pytest가 동기 테스트를 순차 실행하는 것처럼 비동기 테스트도 순차적으로 실행해요. 각 비동기 테스트는 배정된 이벤트 루프 안에서 돌아요.
이 순차 실행은 의도적이고, 테스트 격리를 지키는 데 중요해요. 테스트를 동시에 실행하면 경쟁 조건(race condition)이나 한 테스트가 다른 테스트를 방해하는 부수 효과가 생길 수 있어서, 테스트 결과가 불안정해지고 디버깅이 어려워질 수 있어요.