인수테스트(UAT; User Acceptance Test)
- 요구사항 대로 기능이 구현되었는지를 확인하는 과정
- 전체 시스템 사용자 관점에서 시험하는 블랙박스 테스트
- 사용자 인수 테스트를 자동화하는 것이 어려운 요인들
- 스테이징 환경 : 프로덕션 환경과 동일한 스테이징(테스트) 환경에서 이뤄져야 함
도커 레지스트리의 구성
- 빌드한 이미지를 저장 및 관리할 수 있는 저장소 셋업
- CI/CD 과정과 k8s 클러스터의 운용에 대해 더 깊이 이해
어플리케이션 패키지 빌드 및 이미지 푸시
- 소스 수준에서 테스트 (UT) 가 완료된 어플리케이션을 독립 및 통일된 환경에서 실행될 수 있도록 컨테이너화
- 응용 소프트웨어를 (전체 의존성을) 컨테이너에 포함하여 이미지로 만들고 레지스트리에 저장
UAT 프레임워크 적용
- 보다 체계적이고 완성도가 높은 인수 테스트를 위한 프레임워크 활용
- 응용 소프트웨어의 개발 환경에 따라 크게 달라지는 부분이지만 예제를 중심으로 시나리오 이해
아티팩트 리포지토리
버전 관리, 접근 제어 등의 기능을 가지는 소프트웨어 개발 산출물(예:패키지)을 발행(푸시)하거나
인출(풀)할 수 있는 저장소 및 관리 기법이 필요
파이프라인의 모든 단계에서 동일한 바이너리가 사용되는 것을 보장함으로써 지속적 인도 프로세스에서 매우 중요한 역할
도커 레지스트리
- 컨테이너화된 소프트웨어 산출물인 도커 이미지 관리할 수 있는 리포지토리
- 클라우드 방식 레지스트리
- Docker Hub
- 상용 클라우드에서 제공하는 서비스(AWS ECR, GCP Artifact Registry...)
- 자체 호스팅 방식 레지스트리
- 사내 네트워크 아닌 외부에 소프트웨어 보관하는 것 금지하는 정책 갖고 있는 경우 유일한 해결법
- 직접 관리의 부담이 있고 접근 제어 및 인증서 설정 등 번거로운 작업 수반
이미지 레지스트리 구축 실습
- 도커 이용해 로컬 컴퓨터 컨테이너로 레지스트리 서비스 테스트
- 도커 레지스트리가 도커 이미지로 제공되고 있으므로, 가져다가 실행하고 테스트
- k8s 클러스터에 레지스트리 서비스 배포
- 기본적 동작 테스트 : 클러스터 운용 실습
- OpenSSL 인증서 설치, 설정
- PV, PVC 만들어 레지스트리 데이터가 호스트에 남아 있도록 설정
- docker push와 kubectl run으로 기본적 확인, Jenkins 파이프라인 테스트
데이터 볼륨과 SSL 인증서
- 호스트 디렉토리를 레지스트리에 볼륨으로 공유
- 레지스트리 저장된 데이터는 컨테이너 및 포드 등이 사멸하는 경우에도 유지 가능
- PV 정의하고 PVC 설정하여 레지스트리 서버 컨테이너에서 이용핟록 볼륨 마운트
- 자가 서명된(self-signed) 인증서 발급하여 레지스트리 서버에 설치
- 실제 운용환경에서는 CA(certificate authority)로부터 발급받은 인증서 설치하고 주기적 갱신
공인할 수 있는 인증을 이용할 것 - 간이로 자가 서명 인증서 발급 후 레지스트리 서버에 설치
- 실제 운용환경에서는 CA(certificate authority)로부터 발급받은 인증서 설치하고 주기적 갱신
자가 서명 인증서 발급
- OpenSSL을 이용한 인증서 발급 및 설치
- openSSL 설치
- certs 폴더 생성
- k8s secret으로 등록
openssl req `
>> -newkeys rsa:4096 -nodes -sha256 -keyout cert/registry.key `
>> -addext "subjectAltName = DNS:registry-service.registry.svc.cluster.local" `
>> -x509 -days 3650 -out certs/registry.crt
kubectl create secret tls registry-cert --cert=certs/registry.crt --key=certs/registry.key -n registry
Jenkins 파이프라인 인수테스트
1 개발자가 변경한 코드 GitHub에 푸시
2 Jenkins가 변경 감지 코드를 인출해 빌드 시작, 코드 점검(단위테스트 수행)
3 Jenkins가 빌드를 완료하여 도커 이미지 생성
4 Jenkins가 생성한 이미지를 레지스트리로 푸시
5 Jenkins가 스테이징 환경을 구성하고 도커 컨테이너 실행
6 스테이징 환경의 도커 호스트가 이미지 가져다가 (풀) 컨테이너 실행
7 Jenkins가 스테이징 환경에서 실행 중인 어플리케이션을 대상으로 인수 테스트 실행

Jenkins 에서 도커 이미지 빌드, 푸시 및 인수 테스트 환경 준비
- 컨테이너 설정 필요
로컬 환경 빌드 및 테스트
- Gradle 이용해 Java archive 파일 생성 (빌드)
- Dockerfile 작성, 컨테이너 생성 실행
- 위 컨테이너 대상으로 간단한 테스트 실행해 컨테이너의 정상 동작을 가볍게 확인
Jenkins 파이프라인에 스테이지 추가
- 도커 이미지 빌드 및 푸시
- 스테이징 환경에 컨테이너 실행, 인수 테스트 적용
테스트 자동화를 위한 리포지토리 설정
- GitHub 소스에 두 파일 추가
- Dockerfile
- acceptance_test.sh : 자동화된 인수 테스트를 수행하기 위해 테스트 스크립트도 코드 리포지토리에 등록
작업용 임시 파이프라인 만들기
- 기존에 설정한 파이프라인에서 "Poll SCM" 옵션 끄기
- 시험 위한 새로운 Jenkins 아이템 생성
- 리포지토리 Jenkins 내용 가져와 Pipeline Script로 입력
- 단, Checkout 스테이지의 checkout scm은 이전에 이용했던 방식 적용해
github 리포지토리 주소와 credentials 정보 적용한 내용을 변경
- 지금까지 구성한 단계가 성공적으로 동작함 확인 (Build Nodw)
- Checkout > Compile > Unit Test > Code Coverage > Static Analysis
스테이징 및 인수 테스트 파이프라인 추가
stage("Deploy to Staging"){
steps {
sh '''
docker run -d --rm -p 8765:8080 --name calculator ${REGISTRY_URI}/calculator:${REGISTRY_TAG}
'''
}
}
stage("Acceptance Test"){
steps {
sleep 60
sh '''
chmod +x acceptance_test.sh
&& ./acceptance_test.sh
'''
}
}
테스트 통과 준비 완료
- acceptance_test.sh 내용 올바른 결과 테스트하도록 수정
- git commit -> git push
- Build Now 눌러서 결과 확인
클린업 스테이지 추가
- 스테이징 환경에 실행했던 도커 컨테이너를 삭제
- 섹션 post에 "always"로 단계 추가
- 빌드가 실패하더라도 반드시 실행되는 단계
post {
always {
sh '''
docker stop calculator
'''
}
}
요약
- 인수테스트
- 프로덕션 환경과 동일한 바이너리(빌드의 결과)를 이용해 스테이징
- 스테이징된 소프트웨어(컨테이너화되어 있음)를 대상으로 사용자 요구사항 만족하는지 테스트
- Jenkins 이용한 인수 테스트 자동화
- Dockerfile을 포함하여 빌드와 관련한 모든 파일 GitHub 소스 리포지토리에 등록
- 자동화 테스트가 가능한 방법을 구현하고 이를 실행하는 파이프라인 스테이지 정의
- 그러나 이 테스트가 인수테스트로서 적당하게 이용될 수 있는가?
인수테스트 프레임워크 적용
사용자 대면 테스트
- REST API의 경우 curl에 의한 테스트로도 어느 정도 블랙박스 테스트 가능
- 그러나 일반적으로 이런 테스트에 의존할 수 었음
- 사용자와 함께 작성 가능 사용자가 이해할 수 있는 테스트 작성 방법 필요
- 특정한 목적을 반영하는 인수테스트
BDD (Behavior-Driven Development)
- 사용자가 인수 기준 제시
- 위 인수 기준으로부터 개발자는 픽스처 또는 스템 정의라고 부르는 사용자 친화 DSL(Domain-specific language)와
프로그래밍 언어 통합해 테스트 작성 - 도구
- Cucumber
- Selenium
Cucumber 이용한 인수 테스트 생성
- 인수 기준 생성
- 특정 파일에 비즈니스 사양 명세
- 경로 : \src\test\resources\feature
Feature: Calculator
Scenario: Sum two numbers
Given I have two numbers: 2 and 3
When the calculator sums them
Then I receive 5 as a result
- 스텝 정의 작성
- 기능 사양을 실행할 수 있는 Java 바인딩 생성
- \src\test\java\acceptance
package acceptance;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import org.springframework.web.client.RestTemplate;
import static org.junit.Assert.assertEquals;
public class StepDefinitions {
private String server = System.getProperty("calculator.url");
private RestTemplate restTemplate = new RestTemplate();
private String a;
private String b;
private String result;
@Given("^I have two numbers: (.*) and (.*)$")
public void i_have_two_numbers(String a, String b) throws Throwable {
this.a = a;
this.b = b;
}
@When("^the calculator sums them$")
public void the_calculator_sums_them() throws Throwable {
String url = String.format("%s/sum?a=%s&b=%s", server, a, b);
result = restTemplate.getForObject(url, String.class);
}
@Then("^I receive (.*) as a result$")
public void i_receive_as_a_result(String expectedResult) throws Throwable {
assertEquals(expectedResult, result);
}
}
- 자동 인수 테스트 실행
- Gradle 설정에 라이브러리 의존성 명세 추가
- Gradle 태스크와 Junit 테스트 러너를 추가
Gradle 태스크 설정
// build.gradle
testImplementation("io.cucumber:cucumber-java:7.14.0")
testImplementation("io.cucumber:cucumber-junit:7.14.0")
tasks.named('test') {
useJUnitPlatform()
}
tasks.register('acceptanceTest, Test'){
include '**/acceptance/**'
systemProperties System.getProperties()
}
test{
useJUnitPlatform()
exclude '**/acceptance/**'
}
Junit 테스트 러너를 추가
- src\test\java\acceptance\AcceptanceTest.java
package acceptance;
import io.cucumber.junit.CucumberOptions;
import io.cucumber.junit.Cucumber;
import org.junit.runner.RunWith;
@RunWith(Cucumber.class)
@CucumberOptions(features = "classpath:feature")
public class AcceptanceTest {
}
로컬 환경에서의 테스트 점검
docker run -d --rm -p 8765:8080 localhost:30100/calculator:1.0
./gradlew acceptanceTest "-Dcalculator.url=http://localhost:8765"
테스트는 항상 실패하는 시나리오부터
TDD(Test-Driven Development)
인수 테스트는 기술보다 사람이 중심
TDD는 특히 인수테스트 시기에 적합
인수 기준 사양을 먼저 작성
- 인수테스트 통과를 기능 구현 완료로 간주
- Jira 등 이슈 추적 도구의 요청 티켓에 기능 사양 첨부하는 방법도 적용
요약 및 정리
- 레지스트리 셋업
- 클러스터 활용 실습 및 이미지 레지스트리 동작 기초 이해
- 인수 테스트 위한 빌드 환경 구축
- Jenkins agent 컨테이너의 구성 및 설정 실습
- CI 파이프라인 완성
- CD 파이프라인 구축의 토대가 될 기술적 요소의 학습
- 테스트의 중요성, TDD
- 특히 프로세스의 자동화를 통한 '지속적 인도' 관점에서 테스트의 중요성 이해