티스토리 뷰

Backend

성능 테스트를 위한 격리 - hoverfly

지마켓 선현상 2022. 11. 11. 16:24

격리

성능 테스트의 한 가지 곤란한 점은 그 영향력입니다. 다른 서비스나 테스트에 영향을 줄만한 부하는 모두를 곤란하게 합니다. 따라서, 가능하다면 테스트 영역을 격리하는 것이 좋습니다.

가장 좋은 격리는 전체 시스템을 테스트를 위해 한 벌 준비하는 것이지만 시스템이 커질수록 이는 부담입니다. 약간 현실적인 얘기를 하자면, 현실에서는 모든 팀이 테스트 용이한 배포본을 유지하지 않을 수도 있습니다. 이런 경우에도 격리가 가능할까요?

차선으로 선택할 수 있는 방식은 mock service 를 활용하는 것입니다. mock service 는 기능에 따라 해당 서비스의 latency 도 모사할 수 있습니다.

이러한 mock service 를 만드는 과정에 필요한 것은 흉내 낼 api 호출들을 마련하는 것입니다. 몇몇 api 요청만을 흉내 내면 된다면 모르겠지만 MSA를 지향할수록, 또 도메인 컴포넌트 수가 5개 이상 늘어날수록 비즈니스에 사용하는 api 요청은 점점 늘어납니다. 게다가 성능 테스트는 복합 시나리오를 테스트하기도 합니다. 실 운영과 유사하게 다수의 사용자로부터 다양한 유즈 케이스가 동시에 발생하는 시나리오입니다. 이런 시나리오를 위해서 흉내 낼 api 요청은 사실 직접 마련하기에는 너무 고달픈 작업입니다.

관련 글

Hoverfly

우리가 사용한 솔루션은 hoverfly 입니다. hoverfly 는 일종의 proxy 로 이를 지나는 api 요청들을 기록할 수 있습니다. 기록한 내용을 토대로 시뮬레이션도 가능합니다. 또한 JUnit 과 결합하여 사용하는 Hoverfly Java 도 지원합니다.

hoverfly 를 사용하여 이제 각 컴포넌트들의 요청을 기록해 봅시다. hoverfly 를 셋업하는 것은 길게 설명하지 않겠습니다. dockerhub 의 spectolabs/hoverfly 이미지를 그대로 쓰면 충분합니다. 이렇게 standalone 모드로 동작하는 hoverfly 는 REST API 를 지원합니다. 그리고 admin 포트를 통한 dashboard web service 도 있으므로 브라우저로 접속하여 사용할 수도 있습니다.

Proxying

hoverfly 가 녹화하는 요청은 destination 또한 조건에 포함합니다. hoverfly 는 일종의 middle man 이기 때문입니다. 따라서, 다음과 같은 Http 클라이언트 설정을 합니다. feignClient 을 사용하여 proxy 설정을 하는 예제입니다.

@Bean
@ConditionalOnProperty(prefix = "hoverfly", name = "enabled", havingValue = "true")
public Client feignHoverflyClient(
    @Value("${hoverfly.host}") String host,
    @Value("${hoverfly.port:80}") int port
) {
  Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(host, port));
  return new Client.Proxied(null, null, proxy);
}

이를 통해 캡쳐모드에서는 hoverfly 서버를 향하되 요청 정보를 변경하지 않고 테스트를 수행합니다. 성능 테스트를 수행할 격리 내에 모든 서비스의 호출에 이 설정을 해두면 그야말로 경계 외로 통하는 모든 요청을 캡처할 수 있습니다. 캡처 자체는 성능을 테스트할 용도는 아니기 때문에 천천히 올바르게 캡처합니다.

Capture

먼저 hoverfly 는 캡처를 도와줍니다. 꽤 자세히 기록하고 이를 바탕으로 매칭도 풍부하게 제공합니다. 캡처는 실제 응답을 기반으로 진행한다는 점이 장점입니다. 실제의 요청-응답이 테스트 케이스에 자연스럽게 포함 가능하다는 점이죠.

hoverfly dashboard 로 들어가서 capture 모드를 설정합니다.

이제 이 hoverfly 서버를 경유하는 요청과 응답 쌍을 캡처합니다. 이렇게 캡처한 결과는 GET /api/v2/simulation 요청으로 확인할 수 있습니다. 아래 예시는 표현을 위해 주요한 내용만 추려본 결과입니다. JSON 포맷으로 요청과 응답 쌍으로 보관합니다.

{
  "data": {
    "pairs": [{
      "request": {
        "path": [{
          "matcher": "exact",
          "value": "/v1/fee"
        }],
        "destination": [{
          "matcher": "exact",
          "value": "fee-dev.gmarket.com"
        }],
        "body": [{
          "matcher": "json",
          "value": "{\"requestKey\":\"1000\",\"itemNo\":\"10000001\",\"sellerNo\":\"1000001\",\"qty\":1,\"price\":10000}"
        }]
      },
      "response": {
        "status": 200,
        "body": "{\"requestKey\":\"1000\",\"itemNo\":\"10000001\",\"fee\":50}",
        "headers": {
          "Content-Type": [
            "application/json;charset=UTF-8"
          ]
        }
      }
    }]
  }
}

테스트에 참여하는 서비스 수가 많을 때 모두 같은 hoverfly 서버로 proxy 를 돌려도 좋습니다. 이렇게 하면 하나의 시나리오가 우리의 서비스들 간의 네트워크를 빈번히 통하여 협력하더라도 협력에 참여한 요청들을 같이 모아 시나리오 별로 묶을 수 있습니다. 이어서 이 기록들을 실제 테스트에 사용할 때, 각 시나리오에 맞는 네트워크 내역들을 찾기에 아주 좋은 분류로 도움을 받았습니다.

심지어는 처음 요청으로 쓰인 네트워크마저도 함께 묶어두면 시나리오 별 요청도 같이 관리할 수 있기 때문에 필요할 때는 시작 서비스 앞에 proxy 를 두고 캡처에 포함하기도 하였습니다.

이런 구성은 시나리오를 수행하는 시스템에 별다른 조치 없이 일반환경과 캡쳐환경을 오갈 수 있도록 해 줍니다. 이러한 형태로 구성한 시스템 위에서 QE 분들께 시나리오를 수행해 주시길 요청하는 것 만으로 하나하나 테스트를 반복할 수 있는 케이스를 확보할 수 있습니다.

정리

성능 테스트가 아니어도 사실 테스트 작성을 꺼리게 하는 요인 중 하나는 이 테스트 더블들을 확보하는 것입니다. 테스트 더블의 확보가 얼마나 귀찮은지 보면 해리 퍼시벌은 그의 저서 파이썬으로 살펴보는 아키텍처 패턴에서 테스트를 언급 중, 도메인 모델 영역은 테스트 더블을 마련하려 애쓰지 말고 그냥 실제 도메인 객체를 포함하는 Unit test 를 만드는 것도 나쁘지 않다 할 정도이죠.

 

반대로 무언가 이런 테스트 더블을 적당히 만들어주는 도구들도 여럿 나왔습니다. 그럼에도 결국은 비즈니스에 의미를 담기 위해서는 일정 정도 사람의 손을 거쳐야 하더라고요. hoverfly는 저희 팀에서는 컴포넌트 테스트에 사용할 테스트 더블을 마련하기 위해 처음 도입하였습니다. 이는 JUnit 호환이 좋아 매우 자연스럽게 활용이 가능했습니다. 그리고 비슷한 경험으로 좀 더 넓은 영역의 테스트에도 활용할 수 없을까 하는 생각이 들어서 이번 내용과 같은 사례를 진행한 것이죠.

 

이어지는 글 성능 테스트를 위한 격리 - 시뮬레이션에서는 이렇게 수집한 내용들을 테스트에 활용하기 위한 과정을 소개하겠습니다.

긴 글 읽어주셔서 감사합니다.

댓글