이번 게시글에서는 깃허브 액션을 이용한 CI 구축과정에 대한 내용을 기록하겠다. 이전 글에서 CI 구축을 하였지만, 카카오: 워크서버개발팀의 GitHub Actions 적용기를 보고 CI의 리팩토링을 필요성을 깨달았고, 해당 글에서는 해당 내용에 대해서 중점적으로 기록할 것이다. 또한 SonarCloud에 대해서 적용한 내용도 기술하겠다.
이전에 작성했던 workflow 파일을 리팩토링 한 결과의 전체 코드이다.
name: 'ContinuousIntegration'
on:
push:
branches:
- main
pull_request:
types: [opened, synchronize, reopened]
permissions: write-all
jobs:
setup:
runs-on: ubuntu-latest
outputs:
build-cache-key: ${{ steps.build-cache.outputs.key }}
unit-test-cache-key: ${{ steps.unit-test-cache.outputs.key }}
code-coverage-test-cache-key: ${{ steps.code-coverage-test-cache.outputs.key }}
steps:
- uses: actions/checkout@v4
- name: Generate build cache key
id: build-cache
run: echo "key=$(echo build-${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }})" >> $GITHUB_OUTPUT
- name: Generate unit test cache key
id: unit-test-cache
run: echo "key=$(echo unit-test-${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }})" >> $GITHUB_OUTPUT
- name: Generate code coverage cache key
id: code-coverage-test-cache
run: echo "key=$(echo code-coverage-test-${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }})" >> $GITHUB_OUTPUT
- name: CI start time
shell: bash
run: echo "START_TIME=$(TZ=":Asia/Seoul" date -R|sed 's/.....$//')"
build:
needs: setup
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
token: ${{ secrets.ACTION_TOKEN }}
- name: Cache Gradle packages
id: cache-gradle
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ needs.setup.outputs.build-cache-key }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Cache Check
if: steps.cache-gradle.outputs.cache-hit == 'true'
run: echo 'Gradle cache hit!'
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: 17
distribution: 'temurin'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew build
unit-test:
needs: setup
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
token: ${{ secrets.ACTION_TOKEN }}
- name: Cache Gradle packages
id: cache-gradle
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ needs.setup.outputs.unit-test-cache-key }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Cache Check
if: steps.cache-gradle.outputs.cache-hit == 'true'
run: echo 'Gradle cache hit!'
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: 17
distribution: 'temurin'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Run unit tests
run: ./gradlew test
- name: Upload unit test HTML report
uses: actions/upload-artifact@v4
if: always()
with:
name: unit-test-report
path: ${{ github.workspace }}/build/reports/tests/test/
- name: publish unit test results
uses: EnricoMi/publish-unit-test-result-action@v2
if: always()
with:
files: ${{ github.workspace }}/build/test-results/test/TEST-*.xml
- name: add comments to a pull request
uses: mikepenz/action-junit-report@v4
if: github.event_name == 'pull_request' && always()
with:
report_paths: ${{ github.workspace }}/build/test-results/test/TEST-*.xml
code-coverage-test:
needs: setup
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: recursive
token: ${{ secrets.ACTION_TOKEN }}
- name: Cache Gradle packages
id: cache-gradle
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ needs.setup.outputs.code-coverage-test-cache-key }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Cache Check
if: steps.cache-gradle.outputs.cache-hit == 'true'
run: echo 'Gradle cache hit!'
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: 17
distribution: 'temurin'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Generate Jacoco Report
run: ./gradlew jacocoTestReport
- name: Jacoco Report to PR
if: github.event_name == 'pull_request' && always()
uses: madrapps/jacoco-report@v1.6.1
with:
paths: ${{ github.workspace }}/build/jacocoReport/test/jacocoTestReport.xml
token: ${{ secrets.GITHUB_TOKEN }}
min-coverage-overall: 80
min-coverage-changed-files: 80
title: Code Coverage
update-comment: true
- name: Upload jacoco HTML report
uses: actions/upload-artifact@v4
if: always()
with:
name: jacoco-html-report
path: ${{ github.workspace }}/build/jacocoReport/test/html
- name: SonarCloud
if: always()
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: ./gradlew sonar
해당 내용은 아래에서 자세히 설명하겠다. 코드가 너무 길어진 관계로 코드를 이용해서 설명하지는 않겠다.
Setup job은 CI 프로세스 시작 부분이다. 필요한 캐시 키 생성과 시작 시간을 기록한다.
- Cache keys 생성: build-cache, unit-test-cache, code-coverage-test-cache 키 생성
- 이는 각 단계에 대한 Gradle 캐시 관리용이다. 캐시 키는 runner.os, .gradle, gradle-wrapper.properties 파일 해시 기반으로 생성- CI 시작 시간 기록: 프로세스 시작 시간을 START_TIME 변수에 기록
Build job은 프로젝트 빌드 담당이다.
- Cache Gradle packages: 캐시 키로 Gradle 패키지 캐시 복원. 캐시 히트 시 "Gradle cache hit!" 메시지 출력
- Set up JDK 17: actions/setup-java@v4 사용하여 JDK 17 설치
- Build with Gradle: chmod +x gradlew 실행 후, ./gradlew build로 빌드 실행
Unit Test job은 단위 테스트 실행 및 결과 처리한다.
- Cache Gradle packages & Cache Check: 빌드와 같은 방식으로 캐시 복원 및 확인.
- Set up JDK 17 & Grant execute permission for gradlew: JDK 설정 및 gradlew 실행 권한 부여
- Run unit tests: ./gradlew test로 단위 테스트 실행
- Upload unit test HTML report & Publish unit test results: HTML로 단위 테스트 결과 업로드 및 PR에 결과 게시
Code Coverage Test job은 코드 커버리지 리포트 생성 및 SonarCloud로 전송한다.
- Generate Jacoco Report: ./gradlew jacocoTestReport로 Jacoco 리포트 생성
- Jacoco Report to PR: madrapps/jacoco-report@v1.6.1 사용하여 PR에 코드 커버리지 리포트 게시
처음에 setup 과정을 거친뒤 각각의 테스트들이 병렬적으로 작동되는 모습이다.
만약 테스트가 실패할 경우, 테스트 실패에 대한 정보를 제공한다. 해당 사진은 unit-test 실패에 대한 결과이다. 참고로 code-coverage-test를 실패할경우 해당 정보도 따로 제공한다.
테스트들이 다 성공한다면, 테스트에 대한 정보들을 잘 제공하는 모습이다.
이번 리팩토링을 통해 GitHub Actions를 통한 CI 구축 과정을 보다 효율적으로 관리할 수 있게 되었다. 또한, SonarCloud를 적용하여 정적 코드 분석을 자동화함으로써 코드 품질 관리를 강화할 수 있었다.
각각에 Test에 대해서 Cash 파일을 관리하는 모습이다.
GitHub Actions를 통해 CI 구축 과정을 병렬 처리로 전환하며, 순차적인 실행에서 발생한 문제들을 해결했다. 이러한 전환은 작업 시간을 줄이고, 서로 다른 작업 간의 의존성 문제를 없앴다. 해당 기능을 통해 문제점을 더욱 더 정확히 파악할 수 있을 것으로 기대한다.