스프링 클라우드 컨트랙트 공식 레퍼런스를 한글로 번역한 문서입니다.
전체 목차는 여기에 있습니다.
여러 프로그래밍 언어가 공존하는 환경이라면, Artifactory와 Nexus같은 바이너리 저장소를 사용하지 않는 언어도 존재할 거다. Spring Cloud Contract는 2.0.0부터 SCMSource Control Management 레포지토리에 명세contract와 스텁stub을 저장할 수 있다. 현재 지원하는 유일한 SCM은 Git이다.
레포지토리에는 다음과 같은 설정이 있어야 한다 (이곳에서 체크아웃받을 수 있다):
.
└── META-INF
└── com.example
└── beer-api-producer-git
└── 0.0.1-SNAPSHOT
├── contracts
│ └── beer-api-consumer
│ ├── messaging
│ │ ├── shouldSendAcceptedVerification.groovy
│ │ └── shouldSendRejectedVerification.groovy
│ └── rest
│ ├── shouldGrantABeerIfOldEnough.groovy
│ └── shouldRejectABeerIfTooYoung.groovy
└── mappings
└── beer-api-consumer
└── rest
├── shouldGrantABeerIfOldEnough.json
└── shouldRejectABeerIfTooYoung.json
META-INF
폴더 밑에서는:
groupId
(com.example
)로 애플리케이션의 그룹을 분류한다.- 각 애플리케이션은 전용
artifactId
로 표현한다 (e.g.beer-api-producer-git
). - 다음으로, 각 애플리케이션은 버전별로 관리한다 (e.g.
0.0.1-SNAPSHOT
). Spring Cloud Contract2.1.0
부터는 다음과 같이 버전을 지정할 수 있다 (시맨틱 버저닝semantic versioning을 쓴다고 가정한다):+
orlatest
: 스텁stub의 최신 버전을 찾는다 (리비전 번호가 같다면, 스냅샷이 항상 최신 아티팩트라고 가정한다). 즉,1.0.0.RELEASE
,2.0.0.BUILD-SNAPSHOT
,2.0.0.RELEASE
가 있다면, 가장 최신 버전은2.0.0.BUILD-SNAPSHOT
이라고 간주한다.1.0.0.RELEASE
,2.0.0.RELEASE
가 있다면, 가장 최신 버전은2.0.0.RELEASE
라고 간주한다.latest
혹은+
버전이 있는 경우, 이 폴더를 선택한다.
release
: 스텁stub의 최신 릴리즈 버전을 찾는다. 즉,1.0.0.RELEASE
,2.0.0.BUILD-SNAPSHOT
,2.0.0.RELEASE
가 있다면, 가장 최신 버전은2.0.0.RELEASE
라고 간주한다.release
버전이 있는 경우, 이 폴더를 선택한다.
마지막으로, 그 밑에는 두 가지 폴더가 있다:
contracts
: 각 컨슈머consumer에게 필요한 명세contract는 컨슈머consumer 이름을 딴 폴더에 저장하는 게 좋다 (e.g.beer-api-consumer
). 이렇게 하면stubs-per-consumer
기능을 활용할 수 있다. 추가적인 디렉토리 구조는 자유롭게 구성하면 된다.mappings
: Maven/Gradle Spring Cloud Contract 플러그인은 이 폴더에 스텁stub 서버의 매핑 정보를 푸시한다. 컨슈머consumer 측에서는, Stub Runner가 이 폴더를 스캔하고, 스텁stub 정의를 사용해 스텁stub 서버를 시작한다. 하위 폴더 구조는contracts
밑에 생성된 폴더와 동일하다.
목차
- 7.6.1. 프로토콜 컨벤션
- 7.6.2. 프로듀서
- 7.6.3. 컨트랙트를 프로듀서 코드와 함께 로컬에 저장하기
- 7.6.4. 컨트랙트는 프로듀서 코드와 함께, 스텁은 외부 레포지토리에 보관하기
- 7.6.5. 컨슈머
7.6.1. Protocol Convention
레포지토리 URL에 프로토콜을 지정하면, 명세contract를 저장할 타입과 장소를 제어할 수 있다 (바이너리 저장소를 사용할지 SCM 레포지토리를 사용할지). Spring Cloud Contract는 등록돼있는 프로토콜 리졸버들을 순회해서, 명세contract(플러그인을 통해)와 스텁stub(Stub Runner를 통해)을 가져온다.
SCM 기능과 관련해서는, 현재 Git 레포지토리를 지원하고 있다. Git 레포지토리를 사용하려면 레포지토리 URL을 지정하는 프로퍼티 앞에 git://
을 붙여줘야 한다. 다음은 몇 가지 예시다:
git://file:///foo/bar
git://https://github.com/spring-cloud-samples/spring-cloud-contract-nodejs-contracts-git.git
git://git@github.com:spring-cloud-samples/spring-cloud-contract-nodejs-contracts-git.git
7.6.2. Producer
프로듀서producer 경우, 외부 명세contract를 사용할 때와 같은 메커니즘을 이용해 SCMsource Control Management을 사용할 수 있다. Spring Cloud Contract는 git://
프로토콜로 시작하는 URL을 만나면 SCM 구현체를 사용하도록 라우팅한다.
Maven을 사용 중이라면
pushStubsToScm
goal을 수동으로 추가하고, Gradle을 사용 중이라면pushStubsToScm
태스크를 사용(바인딩)해야 한다. git 레포지토리의origin
에 자동으로 스텁stub을 푸시하지는 않는다.
다음은 Maven, Gradle 빌드 파일에서 관련 설정 부분을 나타낸 예시다:
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<version>${spring-cloud-contract.version}</version>
<extensions>true</extensions>
<configuration>
<!-- Base class mappings etc. -->
<!-- We want to pick contracts from a Git repository -->
<contractsRepositoryUrl>git://https://github.com/spring-cloud-samples/spring-cloud-contract-nodejs-contracts-git.git</contractsRepositoryUrl>
<!-- We reuse the contract dependency section to set up the path
to the folder that contains the contract definitions. In our case the
path will be /groupId/artifactId/version/contracts -->
<contractDependency>
<groupId>${project.groupId}</groupId>
<artifactId>${project.artifactId}</artifactId>
<version>${project.version}</version>
</contractDependency>
<!-- The contracts mode can't be classpath -->
<contractsMode>REMOTE</contractsMode>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<!-- By default we will not push the stubs back to SCM,
you have to explicitly add it as a goal -->
<goal>pushStubsToScm</goal>
</goals>
</execution>
</executions>
</plugin>
contracts {
// We want to pick contracts from a Git repository
contractDependency {
stringNotation = "${project.group}:${project.name}:${project.version}"
}
/*
We reuse the contract dependency section to set up the path
to the folder that contains the contract definitions. In our case the
path will be /groupId/artifactId/version/contracts
*/
contractRepository {
repositoryUrl = "git://https://github.com/spring-cloud-samples/spring-cloud-contract-nodejs-contracts-git.git"
}
// The mode can't be classpath
contractsMode = "REMOTE"
// Base class mappings etc.
}
/*
In this scenario we want to publish stubs to SCM whenever
the `publish` task is invoked
*/
publish.dependsOn("publishStubsToScm")
그래들 publishStubsToScm
태스크는 여기서 좀 더 커스텀할 수 있다. 다음은 로컬 git 레포지토리에 있는 명세contract를 사용하도록 태스크를 커스텀하는 예시다:
gradle
publishStubsToScm {
// We want to modify the default set up of the plugin when publish stubs to scm is called
// We want to pick contracts from a Git repository
contractDependency {
stringNotation = "${project.group}:${project.name}:${project.version}"
}
/*
We reuse the contract dependency section to set up the path
to the folder that contains the contract definitions. In our case the
path will be /groupId/artifactId/version/contracts
*/
contractRepository {
repositoryUrl = "git://file://${new File(project.rootDir, "../target")}/contract_empty_git/"
}
// We set the contracts mode to `LOCAL`
contractsMode = "LOCAL"
}
-
IMPORTANT
2.3.0.RELEASE
버전부터publishStubsToScm
커스텀에 사용하던customize{}
클로저는 더 이상 사용할 수 없다. 이 설정은 위 예제에서와 같이publishStubsToScm
클로저 내에서 직접 적용해야 한다.
이렇게 세팅해주면:
- 임시 디렉토리에 git 프로젝트를 클론받는다.
- SCM stub downloader는
META-INF/groupId/artifactId/version/contracts
폴더로 이동해 명세contract를 찾는다. 예를 들어com.example:foo:1.0.0
애플리케이션이라면,META-INF/com.example/foo/1.0.0/contracts
를 스캔한다. - 명세contract로부터 테스트를 생성한다.
- 명세contract로부터 스텁stub을 생성한다.
- 테스트가 통과하면, 복제한 레포지토리에 스텁stub을 커밋한다.
- 마지막으로 레포지토리
origin
에 푸시한다.
7.6.3. Producer with Contracts Stored Locally
스텁stub과 명세contract를 SCM에 저장하는 방식이 하나 더 있는데, 명세contract는 프로듀서producer 코드와 함께 로컬에 저장하고, 프로듀서 코드에서 명세contract와 스텁stub만 따로 SCM에 푸시하는 방식이다. 다음은 이를 위한 Maven, Gradle 설정 예시다:
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<version>${spring-cloud-contract.version}</version>
<extensions>true</extensions>
<!-- In the default configuration, we want to use the contracts stored locally -->
<configuration>
<baseClassMappings>
<baseClassMapping>
<contractPackageRegex>.*messaging.*</contractPackageRegex>
<baseClassFQN>com.example.BeerMessagingBase</baseClassFQN>
</baseClassMapping>
<baseClassMapping>
<contractPackageRegex>.*rest.*</contractPackageRegex>
<baseClassFQN>com.example.BeerRestBase</baseClassFQN>
</baseClassMapping>
</baseClassMappings>
<basePackageForTests>com.example</basePackageForTests>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<!-- By default we will not push the stubs back to SCM,
you have to explicitly add it as a goal -->
<goal>pushStubsToScm</goal>
</goals>
<configuration>
<!-- We want to pick contracts from a Git repository -->
<contractsRepositoryUrl>git://file://${env.ROOT}/target/contract_empty_git/
</contractsRepositoryUrl>
<!-- Example of URL via git protocol -->
<!--<contractsRepositoryUrl>git://git@github.com:spring-cloud-samples/spring-cloud-contract-samples.git</contractsRepositoryUrl>-->
<!-- Example of URL via http protocol -->
<!--<contractsRepositoryUrl>git://https://github.com/spring-cloud-samples/spring-cloud-contract-samples.git</contractsRepositoryUrl>-->
<!-- We reuse the contract dependency section to set up the path
to the folder that contains the contract definitions. In our case the
path will be /groupId/artifactId/version/contracts -->
<contractDependency>
<groupId>${project.groupId}</groupId>
<artifactId>${project.artifactId}</artifactId>
<version>${project.version}</version>
</contractDependency>
<!-- The mode can't be classpath -->
<contractsMode>LOCAL</contractsMode>
</configuration>
</execution>
</executions>
</plugin>
contracts {
contractsDslDir = file("src/test/resources/contracts")
testFramework = "JUNIT5"
// Base package for generated tests
basePackageForTests = "com.example"
baseClassMappings {
baseClassMapping(".*messaging.*", "com.example.BeerMessagingBase")
baseClassMapping(".*rest.*", "com.example.BeerRestBase")
}
/*
In this scenario we want to publish stubs to SCM whenever
the `publish` task is executed
*/
publishStubsToScm {
// We want to pick contracts from a Git repository
contractDependency {
stringNotation = "${project.group}:${project.name}:${project.version}"
}
contractRepository {
/*
We reuse the contract dependency section to set up the path
to the folder that contains the contract definitions. In our case the
path will be /groupId/artifactId/version/contracts
*/
repositoryUrl = "git://file://${new File(project.rootDir, "../target")}/contract_empty_git/"
}
}
}
이렇게 세팅해주면:
- 디폴트 디렉토리
src/test/resources/contracts
에 있는 명세contract를 선택한다. - 명세contract로부터 테스트를 생성한다.
- 명세contract로부터 스텁stub을 생성한다.
- 테스트가 통과하면:
- 임시 디렉토리에 git 프로젝트를 클론받는다.
- 클론받은 레포지토리에 스텁stub과 명세contract를 커밋한다.
- 마지막으로 레포지토리
origin
에 푸시한다.
7.6.4. Keeping Contracts with the Producer and Stubs in an External Repository
명세contract는 프로듀서producer 레포지토리에 보관하고, 스텁stub은 외부 git 레포지토리에 보관하는 방법도 있다. 이 방식은 기본적인 컨슈머consumer-프로듀서producer 플로우를 따르지만, 아티팩트 레포지토리에 스텁stub을 저장하기 어려울 때 가장 유용하다.
이땐 평소처럼 프로듀서producer를 세팅한 다음, pushStubsToScm
goal을 추가하고 contractsRepositoryUrl
에 스텁stub을 보관할 레포지토리를 설정하면 된다.
7.6.5. Consumer
컨슈머consumer 측에서는 @AutoConfigureStubRunner
어노테이션이나 JUnit 4 rule, JUnit 5 extension, 프로퍼티를 통해 repositoryRoot
파라미터를 전달할 때, SCM 레포지토리 URL 앞에 git://
프로토콜을 붙이면 된다. 다음 예시를 참고해라:
@AutoConfigureStubRunner(
stubsMode="REMOTE",
repositoryRoot="git://https://github.com/spring-cloud-samples/spring-cloud-contract-nodejs-contracts-git.git",
ids="com.example:bookstore:0.0.1.RELEASE"
)
이렇게 세팅해주면:
- 임시 디렉토리에 git 프로젝트를 클론받는다.
- SCM stub downloader는
META-INF/groupId/artifactId/version/
폴더로 이동해 스텁stub 정의와 명세contract를 찾는다. 예를 들어com.example:foo:1.0.0
애플리케이션이라면,META-INF/com.example/foo/1.0.0/
을 스캔한다. - 매핑 정보를 사용해 스텁stub 서버를 시작한다.
- 메시지 처리를 테스트할 때는 메시지 관련 정의를 읽어와 사용한다.
Next :
7.7. 자동 생성된 테스트 코드에서 클라이언트가 보내는 요청/응답은 어떻게 디버깅하나요?
자동 생성된 테스트 코드에서 클라이언트가 보내는 요청/응답 디버깅하기
전체 목차는 여기에 있습니다.