Post

(완)10장 마이크로서비스 테스트 2부

(완)10장 마이크로서비스 테스트 2부

목차

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
10장 마이크로서비스 테스트 2부
10.1 통합 테스트 작성#
__10.1.1 통합 테스트: 영속화#
__10.1.2 통합 테스트: REST 요청/응답형 상호 작용#
__10.1.3 통합 테스트: 발행/구독 스타일 상호 작용#
__10.1.4 통합 계약 테스트: 비동기 요청/응답 상호 작용
10.2 컴포넌트 테스트 개발#
__10.2.1 인수 테스트 정의#
__10.2.2 인수 테스트 작성: 거킨#
__10.2.3 컴포넌트 테스트 설계#
__10.2.4 컴포넌트 테스트 작성: 주문 서비스
10.3 종단 간 테스트 작성#
__10.3.1 종단 간 테스트 설계#
__10.3.2 종단 간 테스트 작성#
__10.3.3 종단 간 테스트 실행
10.4 마치며

정리 전략?

  • 전부 정리는 NO
  • 핵심중에서 꼭 적고 가야겠다 싶은 것만!
  • 나의 생각을 섞어서 적을것..!
  • 떠오르는 것 먼저 간단하게..!

내용

10.1 통합 테스트 작성

  1. 각 서비스의 어댑터 테스트 예를 들어 JPA 영속화 테스트는 정확히 저장되었는지 OrderRepository를 직접 테스트한다.

  2. 9장에서 나온 계약 활용 계약의 용도는 컨슈머/프로바이더 둘 다 테스트해서 서로 바라보는 API가 일치하는가 확인하는 것, 테스트 대상이 컨슈머인지 프로바이더인지에 따라 다르다.

소비자 쪽 테스트: 계약을 이용해 프로바이더를 모킹한 스텁을 구성할 수 있어 프로바이더 실행 필요 없음 프로바이더 쪽 테스트: 디펜던시를 목으로 잡아 놓고 계약을 이용해 어댑터 테스트

__10.1.1 통합 테스트: 영속화

  • 통합 테스트는 영속화 테스트를 포함한다.
  • 레포지토리 단을 테스트해서 DB영속화가 성공적인지 테스트해보자.

__10.1.2 통합 테스트: REST 요청/응답형 상호 작용

  • REST 요청과 응답이 정상적인지 테스트 하는 방법도 있다.
  • 그것은 바로 mockMvc라는 것을 이용하는 것이다.
  • REST 요청/응답형은 REST 끝점 및 요청/응답 본문의 구조에 대한 테스트를 해야한다.
  1. 계약 생성

  2. HTTP Base

    1
    2
    
    when(orderRepository.findById(OrderDetailsMother.ORDER_ID)).thenReturn(Optional.of(OrderDetailsMother.CHICKEN_VINDALOO_ORDER));
    RestAssuredMockMvc.standaloneSetup(controllers(orderController));
    

    위와 같이 계약에 지정된 orderId로 원하는 Order를 반환하도록 구성한다.

  3. 클라이언트 테스트

OrderServiceProxy 가 와이어목 포트에 요청하도록 구성한다. 그리고 각 테스트 메서드는 orderServiceProxy 를 호출해 정확한 값이 반환되는지, 기대한 예외가 던져지는지 확인한다.

WireMock?

마이크로서비스 아키텍처에서 “와이어 목(Wire Mock)”은 의류 디자인의 개념과는 다르게 테스트를 위한 도구로 사용됩니다. 정확한 명칭은 WireMock이며, 이는 마이크로서비스 패턴 및 테스트에서 다음과 같은 의미를 가집니다.

WireMock이란?

WireMock은 Java 기반의 HTTP Mock 서버 라이브러리로, 개발자들이 외부 API 호출을 테스트할 때 실제 API 서버를 호출하는 대신, 가상의 API 서버를 만들어 HTTP 요청과 응답을 미리 정의할 수 있게 해주는 도구입니다. 이를 통해 서비스 간의 의존성을 낮추고, 마이크로서비스 간의 통신을 시뮬레이션할 수 있습니다.

WireMock의 주요 기능
  1. 가짜 HTTP 서버(Mock HTTP Server): WireMock은 테스트를 위해 실제 API 서버 대신 사용할 수 있는 가짜 HTTP 서버를 생성합니다. 이 서버는 HTTP 요청을 받고, 사전에 정의된 응답을 반환합니다.

  2. HTTP 요청/응답 시뮬레이션: WireMock을 사용하면 특정 HTTP 요청에 대해 어떤 응답을 줄지 미리 설정할 수 있습니다. 이를 통해 외부 API가 반환할 값을 예측하고, 다양한 상황을 테스트할 수 있습니다.

  3. 상태 기반의 테스트(Stateful Behavior): 특정 조건에 따라 응답이 달라지도록 상태 기반 테스트를 지원합니다. 예를 들어, 특정 API 호출이 성공한 후에 다른 호출의 응답이 변경되는 등 복잡한 시나리오를 구현할 수 있습니다.

  4. 네트워크 오류 시뮬레이션: 네트워크 지연, 타임아웃, 연결 실패 등의 상황을 시뮬레이션하여 서비스의 오류 처리 능력을 테스트할 수 있습니다.

WireMock을 사용하는 이유

마이크로서비스 환경에서는 각 서비스가 독립적으로 개발되지만 서로 간의 API 통신이 필수적입니다. 만약 다른 서비스나 API 서버가 준비되지 않았거나, 타임아웃, 오류 등의 상황을 테스트해야 한다면, 실제 API를 호출하는 것은 어렵거나 비효율적일 수 있습니다. WireMock을 사용하면 이러한 제약 없이:

  • 외부 서비스의 응답을 가정하여 독립적인 테스트를 수행할 수 있고,
  • 타임아웃, 예외 상황, 잘못된 응답 등 다양한 케이스를 쉽게 재현할 수 있습니다.
WireMock 사용 예시
  1. 특정 API 요청이 성공적인 응답을 받을 때:
    1
    2
    3
    4
    
    stubFor(get(urlEqualTo("/user/123"))
        .willReturn(aResponse()
            .withStatus(200)
            .withBody("{ \"id\": \"123\", \"name\": \"Alice\" }")));
    
  2. 특정 요청에 대해 오류를 반환하도록 설정:

    1
    2
    3
    4
    
    stubFor(get(urlEqualTo("/user/123"))
        .willReturn(aResponse()
            .withStatus(500)
            .withBody("{ \"error\": \"Internal Server Error\" }")));
    
  3. 응답 지연 시뮬레이션:
    1
    2
    3
    4
    5
    
    stubFor(get(urlEqualTo("/user/123"))
        .willReturn(aResponse()
            .withStatus(200)
            .withFixedDelay(3000)  // 3초 지연
            .withBody("{ \"id\": \"123\", \"name\": \"Alice\" }")));
    
결론

따라서 마이크로서비스 패턴에서의 WireMock은 외부 API와의 통신을 시뮬레이션하고 테스트 환경을 쉽게 구축할 수 있게 도와주는 HTTP Mock 서버 도구입니다. 이를 활용하여 서비스 간의 의존성을 줄이고, 다양한 상황에 대한 테스트를 보다 효율적으로 수행할 수 있습니다.

__10.1.3 통합 테스트: 발행/구독 스타일 상호 작용

  • 발행 구독 스타일을 상호 작용은 중간에 contract계약서를 두고 테스트 할 수 있다.
    • 발행은 발행한 내용이 contract에 의거한 대로 발행된 것인지 확인한다.
    • 구독은 contract에 의거한 이벤트 발생시, 의도한대로 상호작용이나 행위가 일어나는지 확인한다.
  • 발행/구독 스타일은 서로가 바라보는 메시지 채널 & 도메인 이벤트 구조가 서로 일치하는지 확인해야 한다.

__10.1.4 통합 계약 테스트: 비동기 요청/응답 상호 작용

  • 서비스의 메시징 프록시를 호출해 메시징 프록시가 계약대로 커맨드 메시지를 보내는지, 그리고 프록시가 응답 메시지를 적절히 처리하는지 확인
  • 비동기 요청 응답 상호 작용도 contract를 이용하여 유사하게 이루어진다.

결국 통합테스트는.

  • 통합 단위 테스트는 서비스의 개별 부분의 동작을 확인합니다. 통합 테스트는 서비스가 자신의 클라이언트/디펜던시와 올바르게 소통하는지, 단위 테스트는 서비스 로직이 정확한지 확인합니다. 하지만 이 두 테스트는 전체 서비스를 실행하지는 않습니다. 전체 서비스가 잘 작동되는지 확인하려면 피라미드를 한 단계 올라가 컴포넌트 테스트를 작성해야 합니다.

10.2 컴포넌트 테스트 개발

__10.2.1 인수 테스트 정의

  • 인수 테스트와 컴포넌트 테스트 차이가 잘 떠오르지 않았다.
  • 인수 테스트는 유스 케이스에서 출발, 컴포넌트의 클라 관점에서 어떤 동작이 외부에 드러나야 하는지 기술한다.

__10.2.2 인수 테스트 작성: 거킨

거킨을 따로 찾아본 결과

인수 테스트(acceptance test) 작성에서 사용하는 “거킨(Gherkin)”은 소프트웨어 개발 및 테스트에서 사용되는 도메인 특화 언어(DSL)입니다. 거킨은 사람이 읽을 수 있는 간단한 문법을 사용하여 테스트 시나리오를 정의하고, 개발자와 비개발자 간의 소통을 원활하게 하는 도구로 주로 활용됩니다. BDD(Behavior Driven Development, 행동 주도 개발) 방식에서 많이 사용되며, 테스트 케이스를 자연어 형태로 작성할 수 있도록 도와줍니다.

거킨(Gherkin) 문법의 주요 요소
  1. Feature (기능): 테스트할 기능이나 애플리케이션의 주요 특징을 설명합니다.

    1
    
    Feature: 로그인 기능
    
  2. Scenario (시나리오): 특정 기능에 대한 테스트 시나리오를 정의합니다. 시나리오는 기능이 특정 조건에서 어떻게 작동하는지 설명합니다.

    1
    
    Scenario: 올바른 자격 증명으로 로그인
    
  3. Given (주어진): 테스트의 초기 상태를 정의합니다. 어떤 조건이 주어졌을 때 시작할지를 나타냅니다.

    1
    
    Given 사용자는 로그인 페이지에 있다
    
  4. When (언제): 특정 동작이나 행동을 나타냅니다.

    1
    
    When 사용자가 올바른 아이디와 비밀번호를 입력한다
    
  5. Then (그러면): 기대되는 결과를 설명합니다.

    1
    
    Then 사용자는 대시보드 페이지로 이동한다
    
  6. And, But: 추가적인 조건이나 결과를 설명할 때 사용됩니다.

    1
    
    And 환영 메시지가 표시된다
    
예제

거킨 문법을 활용한 간단한 예제는 다음과 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
Feature: 사용자 로그인 기능

  Scenario: 올바른 자격 증명으로 로그인
    Given 사용자는 로그인 페이지에 있다
    When 사용자가 올바른 아이디와 비밀번호를 입력한다
    Then 사용자는 대시보드 페이지로 이동한다
    And 환영 메시지가 표시된다

  Scenario: 잘못된 자격 증명으로 로그인 실패
    Given 사용자는 로그인 페이지에 있다
    When 사용자가 잘못된 아이디와 비밀번호를 입력한다
    Then "로그인 실패" 메시지가 표시된다

이 예제는 로그인을 테스트하는 시나리오를 사람이 읽기 쉬운 형태로 표현하고 있습니다. 이러한 거킨 문법을 사용하면 비개발자도 테스트 시나리오를 이해할 수 있어, 개발자와 비즈니스 팀 간의 커뮤니케이션이 원활해집니다.

거킨의 장점
  • 비즈니스와 기술 팀 간의 소통을 강화: 테스트 시나리오를 쉽게 이해할 수 있어 팀 간의 소통을 원활하게 합니다.
  • 자동화 테스트 도구와의 통합: Cucumber와 같은 BDD 프레임워크와 함께 사용하여, 테스트 시나리오를 자동화된 테스트 코드로 변환할 수 있습니다.
  • 명확한 문서화: 소프트웨어의 기대 동작을 명확하고 쉽게 문서화할 수 있습니다.

거킨은 이러한 특징들 덕분에 BDD를 구현할 때 널리 사용되는 도구입니다.

큐컴버는 또 무엇인가

  • 거킨 전용 테스트 자동화 프레임워크.
  • https://ohoroyoi.tistory.com/71

__10.2.3 컴포넌트 테스트 설계

  • 여기서 말하는 컴포넌트는 하나의 사용자 상호작용을 테스트 한다고 볼 수 있다.
  • 예를 들면, 승인된 사용자가 주문을하게 되면, 해당 주문은 승인된다.
  • 가정 -> 예상 결과.
  • 인-프로세스 컴포넌트 테스트
    • 인-메모리 스텁과 목 디펜던시로 서비스 실행하는 것, @SpringBootTest
    • 배포 가능한 서비스를 테스트할 수 없다
  • 아웃-오브-프로세스 컴포넌트 테스트
    • DB, 메시지 브로커 등은 실제 인프라 서비스 사용 / 애플리케이션 서비스 형태의 디펜던시는 스텁으로 대체
    • 실행이 느리고 인프로세스 컴포넌트 테스트보다 더 취약함
  • 아웃-오브-프로세스 컴포넌트 테스트에서 서비스를 스터빙하는 방법
    • DSL을 스터빙한 와이어목으로 HTTP 스텁을 구성하는 것

__10.2.4 컴포넌트 테스트 작성: 주문 서비스

  • 이 절에서는 아웃-오브-프로세스 전략으로 도커 컨테이너에서 실행되는 서비스를 테스트하는 주문서비스의 컴포넌트 테스트를 작성한다.
  • 그레이들 플러그인으로 테스트에서 도커 컨테이너를 시동, 중단하는 방법을 배움.
1
2
3
4
5
6
./gradlew :ftgo-order-service:componentTest
# 이 커맨드는 2-3분정도 거쳐 다음과 같은일을 한다.
# 1 주문 서비스 빌드
# 2 서비스 및 관련 인프라 서비스를 실행
# 3 테스트를 실행한다.
# 4 실행중인 서비스를 중지한다.

10.3 종단 간 테스트 작성

__10.3.1 종단 간 테스트 설계

  • 종단간 테스트는 사용자의 다양한 상호작용을 테스트하는 방식이다.
  • 사용자 탐험 테스트를 대신 작성하는 것이 좋다. : 이것은 사용자가 시스템을 돌아다니면서 이것 저것 해보고, 둘러보는 행위를 코드로 구현한 것임.

__10.3.2 종단 간 테스트 작성

  • 예제 10-17 : 사용자 탐험에 관한 거킨 기반의 명세
  • 피처: 주문 변경 및 취소
  • 시나리오: 주문 생성, 변경, 취소.
    • 가정
    • 생성했다.
    • 생성 결과 이래
    • 그리고 변경하면
    • 결과 이래
    • 그리고 취소하면
    • 결과 이래

__10.3.3 종단 간 테스트 실행

  • 그레이들 도커 플러그인을 이용하면 간편하다.
  • ex) ftgo-end-to-end-test 를 이용해보자.

10.4 마치며

  • 서비스간의 상호 작용을 테스트할때는 예제 메시지에 해당하는 계약을 활용해보세요!
  • API를 통해 잘 동작하는지는 컴포넌트 테스트를 작성하여 확인하세요!!
    • 디펜던시는 스터빙, 서비스는 따로 테스트!
  • 종간단간 테스트는 가급적 줄이고, 사용자 탐험 테스트를 작성하세요!

코드는

1
2
3
4
5
6
7
8
9
10
11
12
13
// end-to-tend-tests 파일 참고할것.
  @Test
  public void shouldDeliverOrder() {

    createOrder();

    noteCourierAvailable();

    acceptTicket();

    assertOrderAssignedToCourier();

  }

참고자료

https://velog.io/@jimin3263/msa-10.-%EB%A7%88%EC%9D%B4%ED%81%AC%EB%A1%9C%EC%84%9C%EB%B9%84%EC%8A%A4-%ED%85%8C%EC%8A%A4%ED%8A%B8-2%EB%B6%80

  • https://www.amazon.com/Microservices-Patterns-examples-Chris-Richardson/dp/1617294543
  • https://livebook.manning.com/book/microservices-patterns/table-of-contents/1
  • https://medium.com/@onurbaskirt/microservices-testing-strategies-part-2-d6e460145136
This post is licensed under CC BY 4.0 by the author.