QA Mock System

쿠팡

-

서론

QA에게 있어 지상 최대 과제는 프로덕트가 양적 질적 측면에서 순조롭게 릴리즈 될 수 있도록 철저하게 최종 검사를 진행하는 것이다. 그렇다면 이런 테스트에는 어떠한 방법들이 사용될까? 테스트에는 기능 테스트, 인터페이스 테스트와 성능 테스트 등이 있다. 어떤 종류의 테스트를 진행하든지, 테스트 시, 데이터는 더할 나위 없이 중요하며, 테스트 데이터가 충분해야 커버리지가 높을 수 있으며, 나아가 프로덕트 품질도 보장할 수 있다. 이는 QA라면 모두 인지하고 있는 사실이다. 그렇다면 어떻게 테스트 데이터를 충분히 준비할 수 있을까? 또 어떻게 효과적으로 데이터를 생성하고 관리할 수 있을까?

특히 자동화에 있어서 테스트 안정성은 매우 중요하지만, 테스트 데이터의 불안정성으로 인해 종종 테스트 자체가 불안정해지기도 한다. 안정적인 테스트 데이터를 확보하기 위해서 테스트용 데이터를 만들어내거나 목 데이터(Mock Data)를 만들어내는 방법도 있겠지만, 상위 서비스들은 엮인 디펜던시가 많고 타 서비스에 강하게 커플링 되어있는 경우가 흔해 이런 서비스에 대한 테스트 데이터를 확보하는 것은 매우 고된 작업이다.

이는 마치 두꺼운 얼음벽이 앞을 가로막고 있어서 돌아가야만 하는 상황인데, 이 우회로가 되려 구불구불 험난한 상황이라 할 수 있다. 그럼 이제 쿠팡에서 어떻게 이 얼음 벽을 조금씩 파내서 길을 뚫어 지름길을 찾아내는지 같이 살펴보도록 하자.

마이크로 서비스에서의 Mock

오늘날 많은 회사에서 마이크로 서비스 아키텍처를 구축하고 있으며, 컨트랙트 테스트 (Contract Testing) 방식으로 마이크로 서비스를 테스트하고 있다. 각 마이크로 서비스를 프로듀서, 또는 컨슈머로 간주하고 각 서비스별로 테스트를 진행한다. 공동 디버깅 작업이나 연동 전 컨트랙트에 맞춰 독립적으로 개발하며, 일반적으로 이 단계에서 해당 프로듀서(Producer) 또는 컨슈머(Consumer)에 대해 목 서비스(Mock Service)를 통해 컨트랙트 기반의 풍부한 테스트 데이터를 생성하여 코드의 모든 분기를 커버함으로써 서비스 품질을 보장한다.

이는 의존하는 서비스가 적은 하위 서비스에게 있어 상대적으로 쉽고 효율성 높은 방법일 수 있다. 그러나 상위 서비스는 의존하는 서비스가 10여 개에서 수십 개에 이를 수 있으므로, 서비스 테스트 시 목 데이터 생성에 많은 시간이 들며 수정에 대한 비용도 높아져, 투입 대비 산출 비율이 현저히 낮아질 수 있다. 따라서 이런 모든 서비스를 모킹(mocking)하여 테스트 데이터를 제공하는 방식은 부적합하다. 그렇다면, 상위 서비스에 대한 한층 더 효율적인 테스트 방법이나 수단은 없을까? 만약 서비스의 특정 인터페이스만 모킹하고 다른 인터페이스는 정상적으로 계속 호출 가능하다면, 테스트 목적도 달성할 수 있으면서 해당 상위 서비스에 의존하는 다른 서비스를 모킹하기 위한 비용과 그 변화에 수반되는 유지 보수 비용 역시 절감할 수 있을 것이다.

API Gateway

클라이언트와 서비스 간, 서비스 상호 간 호출은 일반적으로 리버스 프록시로 API Gateway를 거친다. 이들 간의 리퀘스트 및 응답이 API gateway를 통하는 상황에서, API Gateway 레벨에서 뭔가 할 수 있는 게 없을까?

만약 이 API Gateway에서 리퀘스트와 리턴을 캡처할 수 있고, 이 과정을 동적으로 처리할 수 있다면, 유연성이 매우 높아질 수 있다. 그러나 이미 운영 중인 성숙한 프레임워크에 대해 이 같은 변경이 생기면, 그에 따른 비용 및 리스크가 상당히 높아진다. 기존 호출 체인을 깨지 않는 해결 방법은 없을까? 즉, 코드 및 아키텍처를 수정하고 개발할 필요 없이, 기존 구조를 변경하여 원하는 것을 실현할 수는 없을까? 기존의 테스트 링크에 테스트 전용 게이트웨이를 추가하는 것은 어떨까?

해당 게이트웨이는 요청 경로 전달의 기본 기능만 갖추면 된다. 예를 들어, 장고 (Django) 서비스를 추가하여 모든 요청의 전송을 담당하게 하고, 모든 요청이 이 장고 서비스를 거치게 되면, 이어지는 작업은 상대적으로 수월해진다.

Mock에 대한 아이디어

요청 과정에서 리퀘스트와 리턴을 가져올 수 있다면, 리턴 데이터를 수정하여 모킹 효과를 낼 수 있지 않을까? 아래의 내용은 본인의 경험을 정리한 목 데이터의 유형이다.

● 전체 응답 모킹: 개발 시, 의존 서비스의 새로운 인터페이스가 필요한데, 해당 인터페이스가 병렬 개발 과정에 있다면 의존 서비스 인터페이스의 전체 모킹이 좋은 방법일 수 있음

● 인터페이스의 이상 또는 지연 반환: 이것은 파괴 검사(Destructive Testing) 및 인터페이스의 응답 지연 처리를 위한 테스트에서 사용 · 인터페이스 반환 중 부분 필드 추가: 의존 인터페이스에 새 필드 추가 시 사용

● 인터페이스 반환 필드 내용 부분 수정: 필수 필드가 반환해야 할 내용을 수정하여 테스트 데이터에 대한 요구 사항을 충족시킬 수 있다. 인터페이스가 변경될 때마다 목 데이터를 매번 조정할 필요가 없다는 것이 전체 인터페이스에 대해 모킹하는 것 대비 이점

● 인터페이스 반환 필드의 삭제: 인터페이스의 특정 필드를 검증하는 테스트에서 빠르게 우리 테스트의 요구 사항을 충족시킬 수 있음

● 인터페이스 요청 바디(Body)의 수정: 때로는 요청 바디의 내용을 동적으로 수정하여 테스트 요구 사항을 충족시켜야 하기도 함.

● 인터페이스 요청 헤더(Header)의 수정: 동적으로 요청 헤더를 추가/수정/삭제함.

요청의 그룹핑(Grouping)

하나의 테스트 서버에 다수의 요청이 동시에 인입되었을 때, 예를 들어 일상적인 기능 테스트와 자동화 테스트가 동시 유입되는 경우, 어느 사용자가 보내온 요청인지 어떻게 구분할 수 있을까? 어떤 요청에 대해 모킹을 해야 하는지 어떻게 판단할까? 각각의 요청이 의존 인터페이스를 호출할 때, 요청에 사용자 uid를 추가하고, 해당 uid를 기반으로 그룹핑한 후에 요청된 url, 메소드, 파라미터 및 바디에 따라 모킹 할 특정 인터페이스를 정한다.

결론

본 글을 통해 전달하고자 하는 바는 위와 같은 시스템을 어떻게 구현할 것인가에 대한 설명이 아니고, 위에 기술된 방식에 대해 함께 고민해 볼 수 있도록 내용을 소개하고자 함이다. 현재 우리는 상위 서비스나 프론트 엔드 테스트 시, 의존하는 서비스가 많고 수정도 많은 경우에 대한 저비용 고효율의 테스트 데이터 생성 시스템을 구축할 수 있는 방법을 강구하고 있다. 이는 비단 기능 테스트, 인터페이스 테스트뿐 아니라, 상위의 인터페이스 및 부분 링크 인터페이스의 성능 테스트에도 사용될 수 있을 것이다.

저자:

Eddie Ying

쿠팡에서 현재 다양한 포지션을 채용하고 있습니다. 자세한 공고는 이 사이트에서 확인하실 수 있습니다.

기업문화 엿볼 때, 더팀스

로그인

/