MSA 프로젝트에서 테스트는 특히 외부 의존성을 많이 사용하게 될 경우 복잡해집니다. Redis, DB, Kafka 등 외부 리소스가 있다면, Mocking이나 다수의 서버 및 컨테이너 구동이 필수적이라, 테스트를 생략하고 PR을 진행하는 경우도 빈번하게 발생합니다. 이 글에서는 GitHub Actions를 통해 실제 개발 환경을 자동으로 구성하여 테스트를 자동화하는 방법을 소개합니다.
문제 상황: gradle build -x test
초기 CI 구성 시에는 gradle build -x test 명령어로 테스트를 건너뛰었다. 외부 Redis, DB, Kafka 등의 의존성이 테스트 중 연결되어야 하는데, 이로 인해 테스트 시 에러가 발생하기 때문이다. 그러나 이렇게 테스트를 생략하면, 서버 실행 후 발생할 수 있는 예외를 미리 확인할 수 없다.
문제 해결: Github actions의 서비스 컨테이너 구성하기
GitHub Actions의 서비스 컨테이너는 워크플로우 내에서 Docker 컨테이너를 이용해 애플리케이션이 필요한 서비스를 호스팅 할 수 있는 기능이다. 이를 통해 Redis, 데이터베이스 등 외부 리소스를 필요로 하는 테스트를 쉽게 설정할 수 있다.
각 워크플로우 단계에 필요한 서비스 컨테이너를 설정하면 실제 개발 및 배포 환경에 맞춘 테스트 환경이 자동으로 구성된다.
GitHub은 워크플로의 각 서비스에 대해 새로운 Docker 컨테이너를 생성하고, 작업이 완료되면 자동으로 삭제하여 리소스를 효율적으로 관리할 수 있다.
아래는 mongo, postgreSQL의 서비스 컨테이너를 구성하는 CI 예시이다.
name: Integrated Services Test
on:
push:
branches: [ "develop" ]
pull_request:
branches: [ "develop" ]
jobs:
build:
runs-on: ubuntu-latest
services:
delivery_db:
image: mongo:latest
env:
MONGO_INITDB_DATABASE: delivery_db
MONGO_INITDB_ROOT_USERNAME: delivery_db
MONGO_INITDB_ROOT_PASSWORD: delivery
ports:
- 5435:27017
commerce_db:
image: postgres:latest
env:
POSTGRES_DB: commerce_db
POSTGRES_USER: commerce_db
POSTGRES_PASSWORD: commerce
ports:
- 5433:5432
options: >-
--health-cmd "pg_isready -U commerce_db"
--health-interval 10s --health-timeout 5s --health-retries 5
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Grant execute permission for Gradle Wrapper in root directory
run: chmod +x ./gradlew
- name: Build Gradle Project
run: ./gradlew build -Dspring.profiles.active=dev
직접 docker-compose를 사용할 수도 있다.
name: Integrated Docker Services Test
on:
push:
branches: [ "develop" ]
pull_request:
branches: [ "develop" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Docker Compose로 서비스 실행
run: |
docker compose -f docker/docker-compose.dev.yml up -d
- name: Sleep for 30 seconds
uses: jakejarvis/wait-action@master
with:
time: '30s'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Grant execute permission for Gradle Wrapper in root directory
run: chmod +x ./gradlew
- name: Build Gradle Project
run: ./gradlew build -Dspring.profiles.active=dev
위 워크플로우 예시에는 repo에 있는 docker 경로의 docker compose를 실행하는 과정이 포함되어 있다. 이를 통해 쉽게 내 repo에 있는 docker 컨테이너 환경을 구성할 수도 있다.
다만, docker compose를 설치하고 docker 이미지를 다운로드하여 실행하기 때문에 바로 build로 넘어가면 안 될 것 같아 필자는 약 30초 정도의 텀을 주었다.
spring cloud eureka가 먼저 실행되어야 하는 경우?
Eureka의 연결이 필요한 경우, 아래 코드를 추가하여 우선적으로 eureka을 build 하고 실행시켜 줬다.
- name: Build and Start Eureka Server
run: |
cd eureka-server
chmod +x ./gradlew # Grant execute permission within the eureka-server directory
./gradlew bootJar
nohup java -jar build/libs/eureka-server-0.0.1-SNAPSHOT.jar &
cd ..
env:
SPRING_PROFILES_ACTIVE: dev
(그러면 마지막 단계에서./gradlew build 시 또 한 번 이 eureka를 build 하게 되는데.. 이 부분은 각 build 작업을 서비스 별로 분리해서 진행하는 리팩토링을..)
마치며
서비스 컨테이너 수가 많아질수록 워크플로우는 생각보다 시간이 걸린다. 한 3분 정도 걸리는데, 더 빠르게 단축시킬 수 있는 방법이 없을까 고민하는 중이다.
완성된 CI 워크플로우는 아래 링크에서 볼 수 있다.
https://github.com/pickple-ecommerce/backend/tree/develop/.github/workflows
참조
'Development > Diary' 카테고리의 다른 글
[Diary] INSERT 동작에 동시성 문제 해결 일대기 (0) | 2024.11.06 |
---|---|
[Diary] 300만 데이터 전체 조회 성능 개선기 (projection, 테이블 설계) (3) | 2024.10.26 |
[Diary] Apache Cassandra에서 MongoDB 전환기 (4) | 2024.10.14 |
[Diary] Domain Driven Design에서 Spring Data Repository 의존성 역전하기 (0) | 2024.10.08 |
[Diary][Spring] Spring Data Cassandra에서 @CreatedDate가 안 되는 문제 해결 (Spring Data는 이 데이터가 새 데이터인지 모른다.) (0) | 2024.09.29 |