GitHub Actions로 Java/Maven 프로젝트 CI 설정하기
1. 개요
Continuous Integration (CI)은 코드를 푸시할 때 즉시 오류를 발견하는 데 도움이 되므로 병합할 때까지 기다렸다가 문제가 터지는 일을 피할 수 있습니다. 이 글에서는 Maven을 사용하는 Java 프로젝트의 CI 파이프라인을 GitHub Actions에서 구축하는 방법을 안내합니다. 빌드, 테스트 실행, 의존성 캐싱, 여러 JDK 버전의 빌드 매트릭스 구성, 리포트 발행 등을 다룹니다. 이 글을 읽으면 워크플로우 파일을 실제 프로젝트에 복사해서 몇 분 안에 실행할 수 있습니다.
2. 샘플 프로젝트 준비
간단한 Spring Boot 프로젝트가 있다고 가정하겠습니다. 표준 Maven 구조를 따르는 pom.xml 파일의 최소 구성은 다음과 같습니다:
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>com.nonladev</groupId>
<artifactId>order-service</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.25.3</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
</plugin>
</plugins>
</build>
</project>
junit-jupiter와 assertj-core 의존성은 테스트를 작성하기 위해 사용됩니다. maven-surefire-plugin 플러그인은 mvn test를 실행할 때 테스트를 실행하는 책임이 있습니다.
테스트를 위한 작은 비즈니스 클래스:
package com.nonladev.order;
public class OrderCalculator {
public double calculateTotal(double unitPrice, int quantity) {
if (quantity <= 0) {
throw new IllegalArgumentException("Quantity는 0보다 커야 합니다");
}
return unitPrice * quantity;
}
}
그리고 해당하는 테스트:
package com.nonladev.order;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class OrderCalculatorTest {
private final OrderCalculator calculator = new OrderCalculator();
@Test
void shouldCalculateTotalCorrectly() {
double total = calculator.calculateTotal(50_000, 3);
assertThat(total).isEqualTo(150_000);
}
@Test
void shouldThrowWhenQuantityIsInvalid() {
assertThatThrownBy(() -> calculator.calculateTotal(50_000, 0))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Quantity는 0보다 커야 합니다");
}
}
shouldCalculateTotalCorrectly는 올바른 합계 계산을 확인합니다. shouldThrowWhenQuantityIsInvalid는 quantity가 유효하지 않을 때 예외가 발생하는지 확인합니다. 이는 CI가 코드를 푸시할 때마다 실행할 테스트입니다.
3. 기본 CI 워크플로우
GitHub Actions는 .github/workflows/ 디렉토리의 YAML 파일을 읽습니다. .github/workflows/ci.yml 파일을 만드세요:
name: CI
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
- name: Build and run tests
run: mvn -B clean verify
각 부분을 설명하겠습니다:
on: workflow는main브랜치를 대상으로push또는pull_request가 있을 때 실행됩니다.actions/checkout@v4: 소스 코드를 러너에 클론합니다.actions/setup-java@v4: JDK 21 Temurin 버전을 설치합니다 —mvn을 호출하기 전에 항상 이 단계가 필요합니다.mvn -B clean verify:-B플래그(배치 모드)는 대화식 출력을 비활성화하며, CI 환경에 적합합니다.clean verify는 처음부터 다시 빌드하고 테스트를 실행합니다(verify페이즈는test를 포함합니다).
커밋을 푸시하고 GitHub의 Actions 탭으로 가면 작업이 실행되는 것을 보고 유사한 로그를 볼 수 있습니다:
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.nonladev.order.OrderCalculatorTest
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESS
테스트가 실패하면 작업의 상태가 빨강색이 되고 로그에서 스택 트레이스를 바로 볼 수 있습니다 — 어떤 머신에도 SSH로 접속할 필요가 없습니다.
4. Maven 의존성 캐싱으로 빌드 속도 높이기
매번 새 러너에서 작업이 실행될 때마다(임시), Maven은 .m2/repository에서 모든 의존성을 다시 다운로드해야 합니다. Spring Boot 프로젝트의 경우 이 작업은 매번 1-2분이 걸릴 수 있습니다. setup-java는 미리 캐싱을 지원합니다:
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
cache: 'maven'
cache: 'maven' 속성은 pom.xml 파일의 해시를 기반으로 ~/.m2/repository 디렉토리를 자동으로 캐시합니다. 다음 빌드에서 pom.xml이 변경되지 않으면 Actions가 캐시를 즉시 복원하고 각 jar을 다시 다운로드하는 단계를 건너뜁니다.
“Build and run tests” 단계의 시간을 캐싱 추가 전후로 비교하여 효율성을 확인할 수 있습니다 — 보통 30-60% 감소합니다.
참고: 프로젝트가 Maven Wrapper(
./mvnw)를 사용하면 러너에 미리 설치된 Maven 버전을 신뢰하는 대신mvnw에 따라 캐싱해야 합니다. GitHub가 이미지를 업데이트할 때마다 기본 Maven 버전이 변경될 수 있기 때문입니다.
5. 여러 JDK 버전의 빌드 매트릭스
여러 Java 버전과 호환되어야 하는 라이브러리를 유지 관리하는 경우 strategy.matrix를 사용하여 여러 작업을 병렬로 실행할 수 있습니다:
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
java-version: [ '17', '21' ]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up JDK ${{ matrix.java-version }}
uses: actions/setup-java@v4
with:
java-version: ${{ matrix.java-version }}
distribution: 'temurin'
cache: 'maven'
- name: Build and run tests
run: mvn -B clean verify
matrix.java-version은 테스트할 버전 목록을 나열합니다. GitHub Actions가 2개의 병렬 작업을 자동으로 생성합니다: 하나는 JDK 17, 하나는 JDK 21입니다. ${{ matrix.java-version }} 변수가 각 해당 작업에 대체됩니다.
Actions 탭의 결과는 예를 들어 build (17)과 build (21)과 같이 2개의 개별 작업을 표시하므로 프로젝트가 어느 Java 버전에서 실패하는지 즉시 알 수 있습니다.
6. 테스트 리포트 발행 및 커버리지 확인
기본적으로 테스트 로그는 Actions에서 텍스트 형태로만 표시됩니다. 더 읽기 쉬운 리포트를 위해 dorny/test-reporter 액션을 사용하세요:
- name: Build and run tests
run: mvn -B clean verify
- name: Publish test report
uses: dorny/test-reporter@v1
if: success() || failure()
with:
name: Maven Tests
path: '**/surefire-reports/*.xml'
reporter: java-junit
if: success() || failure(): 이 단계는 테스트가 실패해도 실행됩니다(작업이 취소되면 실행되지 않음). 항상 리포트를 볼 수 있도록 합니다. path는 테스트 실행 후 Surefire가 생성한 XML 파일을 가리킵니다. 리포트는 PR의 별도 검사로 표시되며, 각 테스트 케이스의 통과/실패 여부를 나열합니다.
커버리지를 측정하려면 pom.xml에 JaCoCo 플러그인을 추가하세요:
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.12</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
mvn verify를 실행하면 target/site/jacoco/index.html에서 HTML 리포트를 자동으로 생성합니다. 이를 아티팩트로 업로드할 수 있습니다:
- name: Upload coverage report
uses: actions/upload-artifact@v4
with:
name: jacoco-report
path: target/site/jacoco/
이 아티팩트는 워크플로우 실행의 요약 페이지에서 다운로드할 수 있으며, 브라우저에서 직접 볼 수 있습니다.
7. 빌드 상태 배지
워크플로우가 안정적으로 실행되면 Actions에 들어가지 않고도 모든 사람이 CI 상태를 알 수 있도록 README.md에 배지를 추가하세요:

<org>/<repo>을 실제 GitHub 이름으로 바꾸세요. 배지는 기본 브랜치에서의 최근 실행 결과에 따라 초록색/빨강색으로 자동 업데이트됩니다.
8. 자주 하는 실수
java-version을maven.compiler.source와 일치하도록 설정하지 않음: 로컬 빌드는 통과하지만 CI는release version X not supported오류로 실패합니다.pom.xml과 워크플로우 간의 JDK 버전을 항상 동기화하세요.mvn verify대신mvn test사용: 프로젝트에verify페이즈에서 실행되는 통합 테스트(예: Failsafe 플러그인)가 있으면mvn test는 이를 건너뛰어 거짓된 보안 감각을 만듭니다.- 의존성을 변경할 때 캐시 키가 잘못됨: 수동으로 캐시 액션(
actions/cache)을 작성할 때 키 해시에pom.xml을 포함하는 것을 잊으면, 이전 캐시로 인해 빌드가 오래된 의존성을 사용할 수 있습니다. 이를 피하려면setup-java의cache: 'maven'을 사용하세요. - Docker/안정적인 네트워크가 없는 자체 호스팅 러너에서 CI 실행: Maven은 중앙 저장소에서 의존성을 다운로드해야 합니다. 러너의 네트워크가 차단되면 빌드가 다운로드 단계에서 중단됩니다. 프록시를 확인하거나 내부 Maven 미러를 사용하세요.
참고: 프로젝트가 매우 작고 간단한 테스트만 있으며 자주 배포하지 않는다면, matrix + 커버리지 리포트가 포함된 완전한 CI 워크플로우는 과할 수 있습니다. 섹션 3의 기본 워크플로우로 시작한 후 프로젝트가 커질수록 점진적으로 확장하세요.
9. 결론
수십 줄의 YAML로 Maven 프로젝트를 위한 CI 파이프라인을 자동으로 빌드, 테스트, 의존성 캐싱 및 리포트 발행할 수 있습니다. 간단한 워크플로우로 시작한 후 실제 필요에 따라 JDK matrix, JaCoCo 커버리지, README 배지를 추가하세요. 가장 중요한 것은 복잡한 워크플로우가 아니라 매번 코드를 푸시할 때 CI가 안정적으로 실행되고 신뢰할 수 있다는 것입니다.