토리맘의 한글라이즈 프로젝트 logo 토리맘의 한글라이즈 프로젝트

스프링 클라우드 컨트랙트 공식 레퍼런스를 한글로 번역한 문서입니다.

전체 목차는 여기에 있습니다.

이번 섹션에선 테스트를 자동 생성하고, 실행 중인 애플리케이션을 호출해 EXPLICIT 모드로 테스트를 실행하는 도커 이미지 springcloud/spring-cloud-contract를 사용해본다.

EXPLICIT 모드는 명세contract로부터 자동 생성한 테스트에서 요청을 모킹하지 않고 실제 요청을 전송한다는 의미다.

또한 Stub Runner를 독립 실행형standalone으로 시작하는 도커 이미지 spring-cloud/spring-cloud-contract-stub-runner도 제공하고 있다.

목차


4.1. A Short Introduction to Maven, JARs, and Binary Storage

JVM이 아닌 프로젝트에서도 도커 이미지를 사용할 수 있기 때문에, Spring Cloud Contract가 기본적으로 제공하는 패키징 방식 뒤에 깔려있는 기본 용어들을 짚고 넘어가는 것이 좋다.

아래 있는 정의 중 일부는 메이븐 용어집에서 가져왔다:


4.2. Generating Tests on the Producer Side

도커 이미지에선 /contracts 폴더에서 명세contract를 검색한다. 테스트를 실행한 결과는 /spring-cloud-contract/build 폴더에서 확인할 수 있다 (디버깅할 때 유용하다).

도커 컨테이너를 실행할 땐, 명세contract를 마운트하고 환경 변수를 전달할 수 있다. 그러면 도커 이미지는:

4.2.1. Environment Variables

Docker 이미지를 사용하려면 실행 중인 애플리케이션나 아티팩트 매니저 인스턴스를 가리키는 등, 몇 가지 환경 변수가 필요하다. 다음은 환경 변수를 정리한 테이블이다:

Name Description Default
ADDITIONAL_FLAGS (도커 이미지에만 해당) 그래들 빌드에 전달할 추가 플래그  
DEBUG (도커 이미지에만 해당) 도커 이미지에 사용할 수 있다 - 그래들 빌드에 디버그 모드를 활성화한다 false
EXTERNAL_CONTRACTS_ARTIFACT_ID 명세contract를 가진 프로젝트의 아티팩트 ID  
EXTERNAL_CONTRACTS_CLASSIFIER 명세contract를 가진 프로젝트의 classifier  
EXTERNAL_CONTRACTS_GROUP_ID 명세contract를 가진 프로젝트의 그룹 ID com.example
EXTERNAL_CONTRACTS_PATH 특정 프로젝트 내에서 명세contract가 포함된 경로. EXTERNAL_CONTRACTS_GROUP_ID를 슬래시로 구분하고, /EXTERNAL_CONTRACTS_ARTIFACT_ID를 연결한 값을 디폴트로 사용한다. 예를 들어, 그룹 ID가 cat.dog, 아티팩트 ID가 fish인 경우, 명세contract 경로는 cat/dog/fish가 된다.  
EXTERNAL_CONTRACTS_REPO_WITH_BINARIES_PASSWORD (생략 가능) EXTERNAL_CONTRACTS_REPO_WITH_BINARIES_URL이 인증authentication을 필요로 하는 경우 password를 지정한다. 기본적으로 REPO_WITH_BINARIES_PASSWORD 값을 사용하며, 여기에 값을 따로 설정하지 않은 경우 기본값은 password다.  
EXTERNAL_CONTRACTS_REPO_WITH_BINARIES_URL 아티팩트 매니저의 URL. 기본적으로 환경 변수 REPO_WITH_BINARIES_URL 값을 사용하며, 따로 설정하지 않은 경우 기본값은 localhost:8081/artifactory/libs-release-local이다.  
EXTERNAL_CONTRACTS_REPO_WITH_BINARIES_USERNAME (생략 가능) EXTERNAL_CONTRACTS_REPO_WITH_BINARIES_URL이 인증authentication을 필요로 하는 경우 username를 지정한다. 기본적으로 REPO_WITH_BINARIES_USERNAME 값을 사용하며, 따로 설정하지 않은 경우 기본값은 admin이다.  
EXTERNAL_CONTRACTS_VERSION 명세contract를 가진 프로젝트의 버전. 기본적으로는 최신 버전을 선택한다. +
EXTERNAL_CONTRACTS_WORK_OFFLINE true로 설정하면 컨테이너의 .m2에서 명세contract를 가진 아티팩트를 검색한다. 로컬 .m2를 컨테이너의 /root/.m2 경로에서 사용 가능한 볼륨으로 마운트하는 식으로 활용할 수 있다. false
FAIL_ON_NO_CONTRACTS 명세contract가 없는 경우 빌드가 실패해야 하는지? false
MESSAGING_TYPE 메시지 처리 타입. [rabbit] 혹은 [kafka]를 사용할 수 있다.  
PRODUCER_STUBS_CLASSIFIER 자동 생성 프로듀서producer 스텁stub에서 사용할 아카이브 classifier stubs
PROJECT_GROUP 현재 프로젝트의 그룹 ID com.example
PROJECT_NAME 현재 프로젝트의 아티팩트 id example
PROJECT_VERSION 현재 프로젝트의 version 0.0.1-SNAPSHOT
PUBLISH_ARTIFACTS true로 설정하면 아티팩트를 바이너리 스토리지로 배포한다 true
PUBLISH_ARTIFACTS_OFFLINE true로 설정하면 아티팩트를 로컬 m2로 배포한다 false
PUBLISH_STUBS_TO_SCM true로 설정하면 스텁stub을 scm에 배포하는 태스크를 실행한다 false
REPO_ALLOW_INSECURE_PROTOCOL (생략 가능) true로 설정하면 아티팩트를 안전하지 않은 HTTP를 통해 아티팩트 매니저로 배포할 수 있다 false
REPO_WITH_BINARIES_PASSWORD (생략 가능) 아티팩트 매니저를 보호 중일 때 사용할 수 있는 password password
REPO_WITH_BINARIES_URL 아티팩트 매니저의 URL (기본적으로 로컬에서 실행 중인 아티팩토리의 디폴트 URL을 사용한다) localhost:8081/artifactory/libs-release-local
REPO_WITH_BINARIES_USERNAME (생략 가능) 아티팩트 매니저를 보호 중일 때 사용할 수 있는 username admin
STANDALONE_PROTOCOL 별도 프로토콜을 추가해야 하는 독립 실행 버전 전용  

테스트를 실행할 땐 다음과 같은 환경 변수를 사용한다:

Name Description Default
APPLICATION_BASE_URL 애플리케이션이 실행되고 있는 URL.  
APPLICATION_PASSWORD 애플리케이션에 접근하기 위한 password (생략 가능).  
APPLICATION_USERNAME 애플리케이션에 접근하기 위한 username (생략 가능).  
MESSAGING_TRIGGER_CONNECT_TIMEOUT 메시지를 트리거하기 위해 애플리케이션에 연결할 때 사용할 타임아웃. 5000
MESSAGING_TRIGGER_READ_TIMEOUT 메시지를 트리거하기 위해 애플리케이션의 응답을 읽어들일 때 설정할 타임아웃. 5000
MESSAGING_TYPE 메시지 처리 타입. [rabbit] 혹은 [kafka]를 지정할 수 있다.  
MESSAGING_TYPE 메시지 기반 명세contract를 처리할 때 메시지 처리 타입을 정의한다.  
SPRING_KAFKA_BOOTSTRAP_SERVERS 카프카 전용 - 브로커 주소.  
SPRING_RABBITMQ_ADDRESSES RabbitMQ 전용 - 브로커 주소.  

4.2.2. Customizing the gradle build

컨테이너에서 실행할 gradle.build를 커스텀하고 싶다면, 컨테이너를 띄울 때 커스텀한 빌드 파일을 볼륨으로 마운트하면 된다:

$ docker run -v <absolute-path-of-your-custom-file>:/spring-cloud-contract/build.gradle springcloud/spring-cloud-contract:<version>

4.2.3. Example of Usage via HTTP

이번에는 간단한 MVC 애플리케이션을 다뤄본다. 가장 먼저 다음 명령어를 실행해 아래 git 레포지토리를 클론받고, 현재 작업 디렉토리를 변경해보자:

$ git clone https://github.com/spring-cloud-samples/spring-cloud-contract-nodejs
$ cd bookstore

명세contract/contracts 폴더에 정의돼 있다.

테스트를 실행해보려면 아래 명령어를 실행하면 된다:

$ npm test

하지만 이번엔 설명을 위해, 다음과 같이 여러 단계로 나누어 진행해본다:

# Stop docker infra (nodejs, artifactory)
$ ./stop_infra.sh
# Start docker infra (nodejs, artifactory)
$ ./setup_infra.sh

# Kill & Run app
$ pkill -f "node app"
$ nohup node app &

# Prepare environment variables
$ SC_CONTRACT_DOCKER_VERSION="..."
$ APP_IP="192.168.0.100"
$ APP_PORT="3000"
$ ARTIFACTORY_PORT="8081"
$ APPLICATION_BASE_URL="http://${APP_IP}:${APP_PORT}"
$ ARTIFACTORY_URL="http://${APP_IP}:${ARTIFACTORY_PORT}/artifactory/libs-release-local"
$ CURRENT_DIR="$( pwd )"
$ CURRENT_FOLDER_NAME=${PWD##*/}
$ PROJECT_VERSION="0.0.1.RELEASE"

# Run contract tests
$ docker run  --rm -e "APPLICATION_BASE_URL=${APPLICATION_BASE_URL}" -e "PUBLISH_ARTIFACTS=true" -e "PROJECT_NAME=${CURRENT_FOLDER_NAME}" -e "REPO_WITH_BINARIES_URL=${ARTIFACTORY_URL}" -e "PROJECT_VERSION=${PROJECT_VERSION}" -v "${CURRENT_DIR}/contracts/:/contracts:ro" -v "${CURRENT_DIR}/node_modules/spring-cloud-contract/output:/spring-cloud-contract-output/" springcloud/spring-cloud-contract:"${SC_CONTRACT_DOCKER_VERSION}"

# Kill app
$ pkill -f "node app"

이 bash 스크립트를 실행하면 다음과 같은 일이 일어난다:

4.2.4. Example of Usage via Messaging

도커 이미지를 통해 Spring Cloud Contract로 메시지를 처리하고 싶다면 (e.g. 애플리케이션마다 사용하는 프로그래밍 언어가 다른 경우 등), 다음과 같은 전제가 필요하다:

Example of a Messaging Contract

명세contract에선 triggerMessage(...) 메소드를 호출해야 한다. 이 메소드는 도커 이미지에서 모든 테스트에 사용하는 베이스 클래스에 미리 정의되어 있으며, 프로듀서producer 측의 HTTP 엔드포인트로 요청을 전송해준다. 이러한 명세contract의 예시는 아래를 참고하면 된다.

Groovy YAML
import org.springframework.cloud.contract.spec.Contract

Contract.make {
    description 'Send a pong message in response to a ping message'
    label 'ping_pong'
    input {
        // You have to provide the `triggerMessage` method with the `label`
        // as a String parameter of the method
        triggeredBy('triggerMessage("ping_pong")')
    }
    outputMessage {
        sentTo('output')
        body([
            message: 'pong'
        ])
    }
    metadata(
        [amqp:
         [
           outputMessage: [
               connectToBroker: [
                   declareQueueWithName: "queue"
               ],
                messageProperties: [
                    receivedRoutingKey: '#'
                ]
           ]
         ]
        ])
}
description: 'Send a pong message in response to a ping message'
label: 'ping_pong'
input:
    # You have to provide the `triggerMessage` method with the `label`
    # as a String parameter of the method
    triggeredBy: 'triggerMessage("ping_pong")'
outputMessage:
    sentTo: 'output'
    body:
        message: 'pong'
metadata:
    amqp:
        outputMessage:
            connectToBroker:
                declareQueueWithName: "queue"
            messageProperties:
                receivedRoutingKey: '#'

HTTP Endpoint to Trigger a Message

이런 엔드포인트는 왜 개발해야 하는 걸까? Spring Cloud Contract가 브로커에 메시지를 전송하는 프로덕션 코드를 트리거할 수 있으려면, 다양한 언어로 코드를 생성해야 한다 (자바 코드를 생성하듯이). 이런 코드를 생성하지 않는다면 어떻게든 메시지를 트리거할 수 있어야 하는데, HTTP 엔드포인트를 이용하면 사용자가 원하는 언어를 사용해 엔드포인트를 준비하면 된다.

엔드포인트는 다음과 같이 설정돼 있어야 한다:

아래 코드는 이러한 엔드포인트의 예시다. 사용 중인 언어로 예제 코드를 작성하는 데 관심이 있다면, 주저하지 말고 Spring Cloud Contract Github 레포지토리에 이슈를 등록해달라.

Python

#!/usr/bin/env python

from flask import Flask
from flask import jsonify
import pika
import os

app = Flask(__name__)

# Production code that sends a message to RabbitMQ
def send_message(cmd):
    connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
    channel = connection.channel()
    channel.basic_publish(
        exchange='output',
        routing_key='#',
        body=cmd,
        properties=pika.BasicProperties(
            delivery_mode=2,  # make message persistent
        ))
    connection.close()
    return " [x] Sent via Rabbit: %s" % cmd

# This should be ran in tests (shouldn't be publicly available)
if 'CONTRACT_TEST' in os.environ:
    @app.route('/springcloudcontract/<label>', methods=['POST'])
    def springcloudcontract(label):
        if label == "ping_pong":
            return send_message('{"message":"pong"}')
        else:
            raise ValueError('No such label expected.')

Running Message Tests on the Producer Side

이제 명세contract로부터 테스트를 생성해서 프로듀서producer의 동작을 테스트해 보자. bash 코드를 통해 명세contract를 첨부해 도커 이미지를 시작하되, 이번에는 메시지를 처리하는 코드가 동작할 수 있도록 몇 가지 변수를 추가해본다. 명세contract는 Git 레포지토리에 보관하고 있다고 가정한다.

#!/bin/bash
set -x

CURRENT_DIR="$( pwd )"

export SC_CONTRACT_DOCKER_VERSION="${SC_CONTRACT_DOCKER_VERSION:-4.0.1-SNAPSHOT}"
export APP_IP="$( ./whats_my_ip.sh )"
export APP_PORT="${APP_PORT:-8000}"
export APPLICATION_BASE_URL="http://${APP_IP}:${APP_PORT}"
export PROJECT_GROUP="${PROJECT_GROUP:-group}"
export PROJECT_NAME="${PROJECT_NAME:-application}"
export PROJECT_VERSION="${PROJECT_VERSION:-0.0.1-SNAPSHOT}"
export PRODUCER_STUBS_CLASSIFIER="${PRODUCER_STUBS_CLASSIFIER:-stubs}"
export FAIL_ON_NO_CONTRACTS="${FAIL_ON_NO_CONTRACTS:-false}"
# In our Python app we want to enable the HTTP endpoint
export CONTRACT_TEST="true"
# In the Verifier docker container we want to add support for RabbitMQ
export MESSAGING_TYPE="rabbit"

# Let's start the infrastructure (e.g. via Docker Compose)
yes | docker-compose kill || echo "Nothing running"
docker-compose up -d

echo "SC Contract Version [${SC_CONTRACT_DOCKER_VERSION}]"
echo "Application URL [${APPLICATION_BASE_URL}]"
echo "Project Version [${PROJECT_VERSION}]"

# Let's run python app
gunicorn -w 4 --bind 0.0.0.0 main:app &
APP_PID=$!

# Generate and run tests
docker run  --rm \
                --name verifier \
                # For the image to find the RabbitMQ running in another container
                -e "SPRING_RABBITMQ_ADDRESSES=${APP_IP}:5672" \
                # We need to tell the container what messaging middleware we will use
                -e "MESSAGING_TYPE=${MESSAGING_TYPE}" \
                -e "PUBLISH_STUBS_TO_SCM=false" \
                -e "PUBLISH_ARTIFACTS=false" \
                -e "APPLICATION_BASE_URL=${APPLICATION_BASE_URL}" \
                -e "PROJECT_NAME=${PROJECT_NAME}" \
                -e "PROJECT_GROUP=${PROJECT_GROUP}" \
                -e "PROJECT_VERSION=${PROJECT_VERSION}" \
                -e "EXTERNAL_CONTRACTS_REPO_WITH_BINARIES_URL=git://https://github.com/marcingrzejszczak/cdct_python_contracts.git" \
                -e "EXTERNAL_CONTRACTS_ARTIFACT_ID=${PROJECT_NAME}" \
                -e "EXTERNAL_CONTRACTS_GROUP_ID=${PROJECT_GROUP}" \
                -e "EXTERNAL_CONTRACTS_VERSION=${PROJECT_VERSION}" \
                -v "${CURRENT_DIR}/build/spring-cloud-contract/output:/spring-cloud-contract-output/" \
                springcloud/spring-cloud-contract:"${SC_CONTRACT_DOCKER_VERSION}"

kill $APP_PID

yes | docker-compose kill

그러면 다음과 같은 일이 일어난다:

테스트가 통과했다면, 파이썬 애플리케이션에서 RabbitMQ로 메시지를 제대로 전송했다고 볼 수 있다.


4.3. Running Stubs on the Consumer Side

이번 섹션에선 컨슈머consumer 측에서 도커를 통해 스텁stub을 가져오고 실행하는 방법을 설명한다.

Spring Cloud Contract는 Stub Runner를 독립 실행형standalone으로 시작하는 도커 이미지 spring-cloud/spring-cloud-contract-stub-runner를 제공하고 있다.

4.3.1. Security

Spring Cloud Contract Stub Runner의 도커 이미지는 Stub Runner를 독립 실행형standalone으로 실행하기 때문에, 보안과 관련해서 생각해봐야 하는 것 역시 동일하다. 이에 대한 자세한 내용은 이곳에서 확인할 수 있다.

4.3.2. Environment Variables

도커 이미지를 실행할 땐 JUnit과 스프링의 공통 프로퍼티를 환경 변수로 전달할 수 있다. 모든 문자는 대문자를 사용해야 하며, 점(.)은 밑줄(_)로 변경해야 한다. 예를 들어, stubrunner.repositoryRoot 프로퍼티는 환경 변수로 사용할 땐 STUBRUNNER_REPOSITORY_ROOT로 표기해야 한다.

그 외에도 다음과 같은 변수를 설정할 수 있다:

4.3.3. Example of Usage

[도커 서버 예시]에서 생성했던 스텁stub을 사용해보려고 한다. 스텁stub9876 포트에서 실행해볼 거다. 레포지토리를 클론받고 아래 명령어대로 현재 작업 디렉토리를 변경하면 NodeJS 코드를 확인할 수 있다:

$ git clone https://github.com/spring-cloud-samples/spring-cloud-contract-nodejs
$ cd bookstore

이제 다음 명령어를 실행하면, 이 스텁stub을 사용해 Stub Runner 부트 애플리케이션을 실행할 수 있다:

# Provide the Spring Cloud Contract Docker version
$ SC_CONTRACT_DOCKER_VERSION="..."
# The IP at which the app is running and Docker container can reach it
$ APP_IP="192.168.0.100"
# Spring Cloud Contract Stub Runner properties
$ STUBRUNNER_PORT="8083"
# Stub coordinates 'groupId:artifactId:version:classifier:port'
$ STUBRUNNER_IDS="com.example:bookstore:0.0.1.RELEASE:stubs:9876"
$ STUBRUNNER_REPOSITORY_ROOT="http://${APP_IP}:8081/artifactory/libs-release-local"
# Run the docker with Stub Runner Boot
$ docker run  --rm \
    -e "STUBRUNNER_IDS=${STUBRUNNER_IDS}" \
    -e "STUBRUNNER_REPOSITORY_ROOT=${STUBRUNNER_REPOSITORY_ROOT}" \
    -e "STUBRUNNER_STUBS_MODE=REMOTE" \
    -p "${STUBRUNNER_PORT}:${STUBRUNNER_PORT}" \
    -p "9876:9876" \
    springcloud/spring-cloud-contract-stub-runner:"${SC_CONTRACT_DOCKER_VERSION}"

위 명령어를 실행하면,

서버 측에서는 빌드한 스텁stub은 상태를 가지고 있었다stateful. curl을 통해 스텁stub이 제대로 설정됐는지 확인할 수 있다. 다음 명령어를 실행해보자:

# let's run the first request (no response is returned)
$ curl -H "Content-Type:application/json" -X POST --data '{ "title" : "Title", "genre" : "Genre", "description" : "Description", "author" : "Author", "publisher" : "Publisher", "pages" : 100, "image_url" : "https://d213dhlpdb53mu.cloudfront.net/assets/pivotal-square-logo-41418bd391196c3022f3cd9f3959b3f6d7764c47873d858583384e759c7db435.svg", "buy_url" : "https://pivotal.io" }' http://localhost:9876/api/books
# Now time for the second request
$ curl -X GET http://localhost:9876/api/books
# You will receive contents of the JSON

호스트 로컬에서 빌드한 스텁stub을 사용하고 싶다면, 환경 변수 -e STUBRUNNER_STUBS_MODE=LOCAL을 설정하고 로컬 m2의 볼륨을 마운트해야 한다 (-v "${HOME}/.m2/:/home/scc/.m2:rw").

4.3.4. Example of Usage with Messaging

메시지를 처리하려면 환경 변수 MESSAGING_TYPE으로 kafkarabbit을 전달해주기만 하면 된다. 그러면 Stub Runner 부트 도커 이미지를 설정하면서 브로커에 연결하는 데 필요한 의존성이 세팅된다.

커넥션 관련 프로퍼티를 설정하려면, Spring Cloud Stream 프로퍼티 페이지를 참고해 적절한 환경 변수를 넘기면 된다.

실행 중인 미들웨어의 위치를 지정하는 프로퍼티를 가장 많이 사용할 거다. 설정할 프로퍼티 이름이 spring.rabbitmq.addresses, spring.kafka.bootstrap-servers라면, 환경 변수 이름은 각각 SPRING_RABBITMQ_ADDRESSES, SPRING_KAFKA_BOOTSTRAP_SERVERS로 지정해야 한다.


4.4. Running Contract Tests against Existing Middleware

상황에 따라 기존 미들웨어를 그대로 사용해 명세contract 테스트를 실행해야 할 수 있다. 테스트 프레임워크에 따라서, 빌드 중에 실행한 테스트는 통과하고, 프로덕션 환경에서는 통신에 실패할 수도 있기 때문이다.

Spring Cloud Contract 도커 이미지에서는 기존에 가지고 있는 미들웨어에 연결할 수 있는 옵션을 제공한다. 앞에서도 언급한 대로, Kafka와 RabbitMQ를 기본으로 지원한다. 하지만 Apache Camel Components를 이용하면 다른 미들웨어도 사용할 수 있다. Apache Camel을 사용하는 아래 예시를 함께 살펴보자.

4.4.1. Spring Cloud Contract Docker and running Middleware

임의의 미들웨어에 연결할 수 있도록, contract 부분에 메타데이터 standalone을 정의한다.

description: 'Send a pong message in response to a ping message'
label: 'standalone_ping_pong' # (1)
input:
  triggeredBy: 'triggerMessage("ping_pong")' # (2)
outputMessage:
  sentTo: 'rabbitmq:output' # (3)
  body: # (4)
    message: 'pong'
metadata:
  standalone: # (5)
    setup: # (6)
      options: rabbitmq:output?queue=output&routingKey= # (7)
    outputMessage: # (8)
      additionalOptions: routingKey=#&queue=output # (9) 

(1) Stub Runner를 통해 메시지를 트리거할 수 있도록 레이블을 지정한다
(2) 앞에서 보여준 메시지 처리 예제와 마찬가지로, 지정한 프로토콜에 따라 메시지를 전송할 수 있도록, 실행 중인 애플리케이션의 HTTP 엔드포인트를 트리거해야 한다.
(3) Apache Camel에서 요구하는 protocol:destination 형식
(4) 출력 메시지의 body
(5) 메타데이터 standalone
(6) setup 부분에는 명세contract 테스트를 실행하려면, 실행 중인 애플리케이션의 HTTP 엔드포인트를 실제로 호출하기 전에 어떤 준비가 필요한지에 대한 정보가 담겨있다.
(7) setup 단계에서 호출할 Apache Camel URI. 여기선 output exchange에서 메시지를 폴링하려고 할 것이며, queue=outputroutingKey=를 정의했으므로 output이라는 이름의 큐가 설정되고 라우팅 키  output exchange에 바인딩된다.
(8) (3)번에 정의한 protocol:destination에 추가할 별도 옵션 (좀 더 기술적인 옵션). 여기에 정의한 옵션을 결합하면 rabbitmq:output?routingKey=#&queue=output 형식이 된다.

명세contract 테스트를 통과시키려면, 여러 프로그래밍 언어가 공존하는 환경에서 늘 그렇듯, 애플리케이션과 미들웨어가 실행 중이어야 한다. 이번에는 Spring Cloud Contract 도커 이미지에 다른 환경 변수를 설정해본다.

#!/bin/bash
set -x

# Setup
# Run the middleware
docker-compose up -d rabbitmq # (1)

# Run the python application
gunicorn -w 4 --bind 0.0.0.0 main:app & # (2)
APP_PID=$!

docker run  --rm \
                --name verifier \
                -e "STANDALONE_PROTOCOL=rabbitmq" \ # (3)
                -e "CAMEL_COMPONENT_RABBITMQ_ADDRESSES=172.18.0.1:5672" \ # (4) 
                -e "PUBLISH_STUBS_TO_SCM=false" \
                -e "PUBLISH_ARTIFACTS=false" \
                -e "APPLICATION_BASE_URL=172.18.0.1" \
                -e "PROJECT_NAME=application" \
                -e "PROJECT_GROUP=group" \
                -e "EXTERNAL_CONTRACTS_ARTIFACT_ID=application" \
                -e "EXTERNAL_CONTRACTS_GROUP_ID=group" \
                -e "EXTERNAL_CONTRACTS_VERSION=0.0.1-SNAPSHOT" \
                -v "${CURRENT_DIR}/build/spring-cloud-contract/output:/spring-cloud-contract-output/" \
                springcloud/spring-cloud-contract:"${SC_CONTRACT_DOCKER_VERSION}"


# Teardown
kill $APP_PID
yes | docker-compose kill

(1) 먼저 미들웨어가 실행 중이어야 한다
(2) 애플리케이션이 떠서 실행 중이어야 한다
(3) 환경 변수 STANDALONE_PROTOCOL을 통해 Apache Camel Component를 가져온다. 여기서 가져올 아티팩트는 org.apache.camel.springboot:camel-${STANDALONE_PROTOCOL}-starter다. 즉, STANDALONE_PROTOCOL이 Camel의 컴포넌트와 매칭된다.
(4) Camel의 스프링 부트 스타터를 활용해 주소를 설정하고 있다 (credential도 설정할 수 있다). Apache Camel의 RabbitMQ 스프링 부트 자동 설정 예제를 참고해라.

4.4.2. Stub Runner Docker and running Middleware

실행 중인 미들웨어로 스텁stub 메시지를 트리거하고 싶다면, 다음과 같은 방식으로 Stub Runner 도커 이미지를 실행하면 된다.

Example of usage

$ docker run \
    -e "CAMEL_COMPONENT_RABBITMQ_ADDRESSES=172.18.0.1:5672" \ # (1)
    -e "STUBRUNNER_IDS=group:application:0.0.1-SNAPSHOT" \ # (2)
    -e "STUBRUNNER_REPOSITORY_ROOT=git://https://github.com/marcingrzejszczak/cdct_python_contracts.git" \ # (3) 
    -e ADDITIONAL_OPTS="--thin.properties.dependencies.rabbitmq=org.apache.camel.springboot:camel-rabbitmq-starter:3.4.0" \ # (4) 
    -e "STUBRUNNER_STUBS_MODE=REMOTE" \ # (5)
    -v "${HOME}/.m2/:/home/scc/.m2:rw" \ # (6)
    -p 8750:8750 \ # (7) 
    springcloud/spring-cloud-contract-stub-runner:3.0.4-SNAPSHOT # ( 8) 

(1) Apache Camel의 스프링 부트 자동 설정을 이용해 RabbitMQ의 주소를 주입하고 있다.
(2) Stub Runner에 어떤 스텁stub을 다운받을지 알려준다
(3) 스텁stub을 받을 외부 저장소를 제공한다 (Git 레포지토리)
(4) ADDITIONAL_OPTS=--thin.properties.dependencies.XXX=GROUP:ARTIFACT:VERSION 프로퍼티를 통해 Stub Runner가 런타임에 가져올 추가 의존성을 알려준다. 이 경우 camel-rabbitmq-starter를 가져오려고 하는데, XXX는 임의의 문자열이고, org.apache.camel.springboot:camel-rabbitmq-starter 아티팩트는 3.4.0 버전을 가져오려고 한다.
(5) Git을 사용하기 때문에, 스텁stub을 가져오는 옵션으로 remote를 설정해야 한다
(6) Stub Runner의 기동 속도를 높이기 위해 메이븐 로컬 레포지토리 .m2를 볼륨으로 마운트한다. 볼륨에 데이터가 생기지 않는다면 읽기 전용 :ro가 아닌 :rw를 명시해 쓰기 권한을 설정했는지 확인해봐라.
(7) Stub Runner가 실행 중인 8750 포트를 노출한다.
(8) Stub Runner 도커 이미지의 좌표coordinates.

잠시 후 콘솔에 다음과 같은 로그가 찍히는데, 이는 Stub Runner가 요청을 수락할 준비가 되었다는 뜻이다.

o.a.c.impl.engine.AbstractCamelContext   : Apache Camel 3.4.3 (camel-1) started in 0.007 seconds
o.s.c.c.s.server.StubRunnerBoot          : Started StubRunnerBoot in 14.483 seconds (JVM running for 18.666)
o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
o.s.web.servlet.DispatcherServlet        : Completed initialization in 2 ms

트리거 목록을 조회하고 싶다면 localhost:8750/triggers 엔드포인트로 HTTP GET 요청을 보내보면 된다. 스텁stub 메시지를 트리거하려면 localhost:8750/triggers/standalone_ping_pong으로 HTTP POST 요청을 보내면 된다. 그러면 콘솔에서 다음과 같은 정보를 확인할 수 있다:

o.s.c.c.v.m.camel.CamelStubMessages      : Will send a message to URI [rabbitmq:output?routingKey=#&queue=output]

RabbitMQ management 콘솔을 확인해보면, output 큐에 1개의 메시지가 존재하는 것을 확인할 수 있다.


Next :
5. Gradle Project
Gradle로 컨슈머-프로듀서 간 컨트랙트 테스트 자동화하기

전체 목차는 여기에 있습니다.

<< >>

TOP