이 포스팅은 개인 공부 기록을 위해 작성했습니다. 정확하지 않은 정보일 수 있음에 참고 부탁드립니다.
조언과 토론은 언제나 환경입니다!
구성
1. 상황: 의존성 주입이 적용된 클래스의 테스트 진행 간 올바른 의존성 주입이 이뤄지지 않음
- 나의 의도
2. 조치
3. 풀리지 않은 의문점들
요약
- 상황
- 원인
- 해결
- 한계
상황: 의존성 주입이 적용된 클래스의 테스트 진행 간 올바른 의존성 주입이 이뤄지지 않음(추측)
전달 전에는 객체(정상)이지만, 전달 후에는 async_db가 function으로 되는 것인지 이해할 수 없었다.
나의 의도
내가 테스트 하고자 한 Servic 클래스는, 추상클래스(AsyncExampleRepository)를 사용하며, 실제 사용시에는 구현체(AsyncExamplePersistenceAdapter)를 주입받는다. 그리고 구현체인 AsyncExamplePersistenceAdapte에서는 사용하는 async_db를 주입 받는다.
AsyncExamplePersistenceAdapte 를 주입하는 코드
async def get_async_example_repository( async_db: AsyncSession = Depends(get_async_db)) -> AsyncExamplePersistenceAdapter: return AsyncExamplePersistenceAdapter(async_db)
테스트 진행을 위해 나는 async_session(AsyncDB의존성 주입)과 get_async_example_repository 함수의 의존성을 오버라이딩하기 위한 코드를 작성했다.
async_example_repository 메서드는 추상클래스인 Respository의 구현체(AsyncExamplePersistenceAdapter)를 오버라이딩 하기 위해 사용되며, get_async_example_repository 메서드는 실제로 AsyncExamplePersistence를 주입할 때 사용하고자 하였다.
이를 위해 나는 다음과 같은 테스트 코드를 작성했다.
# test_async_example_service.py
@pytest.mark.asyncio
class TestAsyncExampleByFunctionalTest:
@pytest.fixture(autouse=True)
def setup(self, async_db: AsyncSession, get_async_example_repository: AsyncExampleRepository):
self.async_db = async_db
self.async_example_repository = get_async_example_repository
self.async_example_service = AsyncExampleService(async_db=async_db,
async_repository=self.async_example_repository)
async def test_async_example_조회_실패_테스트(self):
# given
not_existed_example_id = 999999
# when, then
with pytest.raises(ExceptionResponse) as exc_info:
await self.async_example_service.get_async_example(not_existed_example_id)
assert exc_info.value.error_code == ErrorCode.NOT_FOUND
assert str(exc_info.value.message) == f"No AsyncExample found with id {not_existed_example_id}"
# conftest.py
@pytest_asyncio.fixture
async def async_session():
async_session = AsyncTestingSessionLocal()
try:
yield async_session
finally:
await async_cleanup_database(async_session)
await async_session.close()
@pytest_asyncio.fixture
async def async_example_repository():
async def override_get_db():
yield async_session()
app.dependency_overrides[get_async_db] = override_get_db
return AsyncExamplePersistenceAdapter(async_session)
@pytest_asyncio.fixture
async def get_async_example_repository(async_example_repository):
async def override_async_example_repository():
yield async_example_repository
app.dependency_overrides[get_async_example_repository] = override_async_example_repository
return AsyncExamplePersistenceAdapter(async_session)
하지만 테스트를 실행 했을 때에는 첫번째 사진와 같은 모습이었다. 올바른 의존성 주입이 이뤄지지 않고 있었다.
조치
분명히 나는 async_db에 대한 정의한 픽스처를 동일하게 사용했고, 또 동일한 객체를 전달하였는데 어째서 전달 전에는 객체(정상)이지만, 전달 후에는 async_db가 function으로 되는 것인지 이해할 수 없었다.
나는 이것을보고 의존성이 올바르게 주입이 되지 않았다는 생각을 하게되었고, conftest.py를 수정했다.
- async_example_repository: 불필요한 코드를 삭제하고, 기존의 픽스처를 그대로 사용하여 AsyncExamplePersistenceAdapter에 필요한 async_db 의존성을 주입하고자 하였다.
- get_async_example_repository: 기존의 get_async_example_repository 의존성을 오버라이드 하는 것이 목적이며, async_session을 왜 사용해야하는지에 대해서는 아직 의문이다..(작성하지 않으면 테스트에서 async_session 객체로 인식하지 못하고, 픽스처(함수)로 인식한다.)
그리고 결정적으로, test_async_example_service.py 내에 async_session 픽스처를 Import 하는 코드를 추가했다. 이는 이전에 직면한 픽스처를 찾지못해 발생한 문제와 관련이 있는 듯하다.
- 위와 같이 conftest.py를 수정한 뒤 async_session을 찾지 못한다는 에러를 보고서 조치했다.
- async_example_repository 와 get_async_example_repository 에서 모두 async_session을 사용하고 있어, 수정한 픽스처를 사용하기 위해서는 픽스처에서 사용하는 픽스처까지 Import 해야 사용이 가능했다.
풀리지 않은 의문점들
현 이슈에 대하여, 추측한 문제(의존성이 올바르게 주입되지 않고 있다.)를 해결하기 위해 코드를 수정했으나, 찜찜한 구석이 많다.
get_async_example_repository의 반환 값은 왜 필요한가?
async_example_repository에서는 반환하는 AsyncExamplePersistenceAdapter에 대하여, async_session픽스처를 주입하고 있다. 그리고 get_async_example_repository 에서는 async_example_repository를 통해 의존성을 오버라이딩 하고 있다.
그렇다면, 현 테스트 코드에서 AsyncExampleRepository에 대하여 의존성 주입(get_async_example_repository 픽스처 사용)을 함으로써반환 값은 필요 없는게 아닌가? 하는 생각이 든다. 이와 관련하여, 다양한 코드 조작을 진행해보았으나, 모두 실패했다..
테스트에서 의존성 주입에 대한 Best Practice는 무엇일까?
이번 문제를 해결하기 위해 FastAPI Depends test, FastAPI Dependency override test 등의 키워드를 검색하면서 테스트코드에서 의존성 주입을 어디서 관리하는가? 에 대한 주제로 토론하는 글을 보게 되었다.
Best way to override FastAPI dependencies for testing with a different dependency for each test
According to FastAPI official documentation the recommended way to override the dependencies for testing is to do it globally before all tests are run: async def override_dependency(q: Optiona...
stackoverflow.com
나는 과거 스프링 테스트를 진행할 때를 떠올리며, TestConfig를 작성하여 의존성을 관리해야하지 않을까? 생각하며 오랜만에 과거 개인 프로젝트를 다시 찾아보았다.
이 코드를 다시 보면서, 지금의 테스트 코드를 위한 의존성 주입에는 부족한 부분이 있다는 것을 깨닫게 되었다. 그리고 이에 대해서는 여전히 잘 모르겠다..
- 현재 테스트 코드에서는 의존성 주입을 했는데, 알지 않아도 되는 정보에 대해 의존하고있다.(의존성 주입으로 인한 장점을 가져가지 못함)
- "의존성을 주입하여 관리는 어디서 해야하는가?"에 대한 고민을 하지 않았다. (의존성 주입은 다른 클래스가 많아질 수록 많아질 것이다.)
요약
상황
- 의존성 주입을 받아 동작하는 클래스 테스트 간 의존성 주입이 올바르게 동작하지 않는 문제가 발생했다.
원인
- 의존성을 오버라이딩 하는 픽스처 작성에 오류가 존재했다.(이로인해, 실제 사용 간 객체가 전달되는 것이 아닌, 함수가 전달되는 에러가 발생했다.)
- 과거 pytest 진행간 발생한 픽스처를 찾지 못하는 문제와 동일하게, 사용하는 픽스처를 import 하지 않았다.(다른 클래스에서 사용하는 픽스처를 alias 할 경우 안됨)
해결
- conftest.py 내 픽스처를 수정하고, 테스트 코드 내, 사용하는 픽스처는 물론, 사용하는 픽스처에서 사용하는 픽스처까지 Import 하여 에러를 해결했다.
한계
- 해결한 방법이 제대로된 해결책이라는 생각이 들지 않는다.
- 여전히 다른 테스트 환경과는 달리, 사용하는(사용되는)모든 픽스처를 import 해야한다.
- 테스트에서 의존성을 어떻게 관리할지에 대한 고민과 전략이 부족하다.
관련된 고민의 흔적들...
[ Pytest ] fixture 'sync_client' not found, 정의한 fixture를 찾지 못하는 문제
테스트 환경개발환경python==3.9async-asgi-testclient==1.4.11pytest==8.1.1pytest-asyncio==0.23.6 디렉토리 구조├── alembic├── frontend├── scripts├── src│ ├── domains│ ├── external_service│
sykeem.tistory.com
'개인공부 > 트러블슈팅' 카테고리의 다른 글
Docker 볼륨 마운트 문제: 파일 권한 이슈, 왜 코드 수정 반영이 안될까? (0) | 2025.01.28 |
---|---|
[ Pytest ] fixture 'sync_client' not found, 정의한 fixture를 찾지 못하는 문제 (1) | 2024.05.04 |
[ Trouble-Shooting ] 컨테이너 간 통신에서 발생한 Failed to send HTTP request to endpoint (0) | 2023.10.26 |