Home » 테스트 » PYTEST » how to work moto mock aws function internally?

how to work moto mock aws function internally?

pytest에서 자주 쓰는 패키지 중 하나는 moto입니다. 이 글에서는 5.0.5 버전을 기준으로 설명합니다. aws 서비스를 mock한 객체입니다. 오늘은 이 중, moto 패키지에 있는 mock_aws 함수를 간단하게 알아보도록 하겠습니다.


mock aws 가 어떻게 동작하는가?

흐름을 따라가 보면서 이야기 해 보겠습니다. 먼저, moto를 import 하면 어떤 일이 일어날까요?

[그림 1] builtin_handler

botocore의 handlers.py 에는 핸들러 목록이 정의되어 있습니다. 이벤트가 발생했을 때, 어떤 handler를 호출할 것인지를 알려주는 정보라고 할 수 있어요. 그러면, 특정 이벤트에서 이 핸들러가 바뀌면 어떤 일이 일어날까요? 기존 흐름 대신 다른 흐름을 태울 수 있습니다.

[그림 2] moto.core의 models.py

다음에, before-send가 발생했을 때, botocore_stubber 라는 핸들러를 추가합니다. 여기까지 흐름을 정리하면 아래와 같습니다.

moto를 import 하면, manager의 event_table에 아래 정보를 추가합니다.

  • 이벤트 이름은 before_send
  • 이 이벤트가 호출되었을 때 botocore_stubber를 호출할 겁니다.

여기까지 이해가 되셨다면, 다음 단계로 넘어가 봅시다. 우리가 boto3.resource(“s3”, .. )을 호출했을 때, manager 클래스에서 처리하는 이벤트 순서는 아래와 같아요. 이 중 일부만 가져와 보겠습니다.

디버깅을 하시다 보면, boto3.resource(“s3”, .. )을 호출했을 때 아래와 같은 순서로 이벤트가 발생함을 알 수 있습니다. 이 중, before_send 이벤트 직전만 보도록 하겠습니다.

  • before_sign
  • before_send

사실, before_sign 이전에 6개의 이벤트가 더 있습니다. 어찌 되었던, 각각의 이벤트가 호출되면, hooks에 등록된 handler가 호출됩니다. 이 과정을 디버거로 보겠습니다.

[그림 3] before-send 이벤트

before-send 이벤트가 발생했을 때, hooks의 _emit 함수의 235번째 줄에 디버그를 끊어봅시다. 중요한 것은 handlers_to_call인데요. moto.core의 무언가가 핸들러로 등록되어 있음을 볼 수 있어요. 이것을 어디서 호출할까요?

[그림 4] handler 호출 부분

239번째 줄을 보겠습니다. 보면, response = handler(**kwargs) 이 부분이 보입니다. 여기서 호출하게 됩니다. 그리고, 응답을 리턴하게 됩니다.

[그림 5] botocoreStubber의 __call__ 부분

이 부분으로 들어오게 됩니다. self.enabled가 False인 경우는 나중에 보기로 합시다. 일단 이 단락에서는, moto를 import 하면 before-send에 mock된 무언가가 핸들러로 등록된다. 정도로만 정리하고 넘어갑시다.


mock aws는 뭘 하는가?

여기서 질문, moto로 handler를 inject 시켰기 때문에, mock_aws가 필요 없다고 느꼈을 수도 있습니다. 그리고, 굳이 start와 stop을 해 줄 필요가 없다고 느끼셨을 수도 있습니다. 하지만 그렇지 않습니다. mock_aws를 한 후에, start를 호출했을 때 어떤 일이 벌어지는지 보도록 합시다.

[그림 6] 초기화 함수

이 함수를 호출하면, 세팅에 따라 ServerModeMockAWS, ProxyModeMockAWS, MockAWS를 호출하게 됩니다. 그리고, config 정보를 가지고, 해당 클래스의 생성자를 호출하게 되는데요. 여기에는 별 게 없습니다.

중요한 것은 start와 stop 함수입니다.

[그림 7] start 함수

이 함수를 보면, 중간에 _mocks_active를 True로 설정합니다. 그리고 _enable_patching 을 호출합니다. 이건 대체 무엇을 하는 것일까요?

[그림 8] enabled를 True로 설정하는 부분

217번째 줄을 보면, botocore_stubber.enabled를 True로 설정합니다. 여기까지 흐름을 정리해 봅시다.

  • mock_aws로 mock manager 객체를 생성하고
  • mock.start()로 botocore_stubber의 enabled를 True로 설정합니다.
  • response_mock도 합니다.

그런데, botocore_stubber는 어디서 쓰는 것인가요?

위 글에 따르면, before_send 이벤트가 호출되었을 때 쓰는 것이였습니다. __call__ 함수는, enabled가 False이면 아무것도 하지 않습니다. 기본적으로 stubber는 False로 되어 있기 때문에, True로 설정되지 않는 이상, injected된 logic이 수행되지 않습니다.

그러면 어떻게 되는가?

[그림 9] none response에 대한 처리

none response가 걸렸기 때문에, self._send를 호출합니다. self는 Endpoint입니다.

[그림 10] 실제 s3과 연결하는 부분

이제 실제 s3과 연결하게 됩니다. mock.start()를 하지 않았을 때 요래 됩니다. 따라서, s3을 테스트 하기 위해서는, boto3 리소스나 클라이언트와 연결하기 전에 mock_aws를 호출하고, 모킹을 시작해야 합니다.

참고로, moto를 import 하지 않으면, 아래와 같이 이벤트가 호출됩니다.

request_created가 추가됩니다. 정리하면, mock가 되었느냐 안 되었느냐에 따라 이벤트 순서도 다르고, 핸들러 등도 다르다는 것을 알 수 있어요. 이를 통해, 특정 핸들러를 호출해서 mock을 하고 있음을 알 수 있어요.


mock aws 사용하기

이제 사용해 봅시다. 아까 전에 진짜 s3과 같은 aws 리소스와 연결하기 전에 모킹을 해야 한다고 했어요. 어떻게 하면 될까요?

[그림 11] mock 하기

4번째 줄에서, 가짜 aws manager 객체를 만듭니다. 다음에, 5번째 줄에서 mock.start() 문이 있습니다. 이는, 진짜 aws와 연결되기 전에, 모킹을 해서 핸들러를 돌려버립니다. 정확히 말하면, moto를 import 해서 특정 이벤트가 발생했을 때, 수행되는 로직을 바꿉니다. 중요한 것은 boto3.resource 문장 수행 전에 했다는 것입니다.

이 단계에서는, aws와 연결하는 로직이 있기 때문이에요. 결과를 볼까요?

[그림 12] 성공적으로 수행된 Object put

aws와 연결하는 것이 없었음에도 불구하고 성공적으로 수행되게 됩니다. 당연하게도, 함수 __enter__와 __exit__가 있다면 with 절로 커버할 수 있습니다. 이를 조금 더 응용하면, pytest에서 setUp에서 모킹을 시작하고, tearDown에서 모킹을 종료하는 식으로 구현할 수 있습니다.

Leave a Comment

12 + 11 =