[공부정리] 깃허브 액션을 이용한 CI 구축

jeyong·2024년 2월 3일
0

공부 / 생각 정리  

목록 보기
21/120
post-thumbnail
post-custom-banner

이번 게시글에서는 깃허브 액션을 이용한 CI 구축과정에 대한 내용을 기록하겠다. 이전 글에서 CI 구축을 하였지만, 카카오: 워크서버개발팀의 GitHub Actions 적용기를 보고 CI의 리팩토링을 필요성을 깨달았고, 해당 글에서는 해당 내용에 대해서 중점적으로 기록할 것이다. 또한 SonarCloud에 대해서 적용한 내용도 기술하겠다.

1. workflow 리팩토링

이전에 작성했던 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

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

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

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

Code Coverage Test job은 코드 커버리지 리포트 생성 및 SonarCloud로 전송한다.

  • Generate Jacoco Report: ./gradlew jacocoTestReport로 Jacoco 리포트 생성
  • Jacoco Report to PR: madrapps/jacoco-report@v1.6.1 사용하여 PR에 코드 커버리지 리포트 게시

2. 실행결과

처음에 setup 과정을 거친뒤 각각의 테스트들이 병렬적으로 작동되는 모습이다.

2-1. ContinuousIntegration 기능 테스트 - 실패

만약 테스트가 실패할 경우, 테스트 실패에 대한 정보를 제공한다. 해당 사진은 unit-test 실패에 대한 결과이다. 참고로 code-coverage-test를 실패할경우 해당 정보도 따로 제공한다.

2-2. ContinuousIntegration 기능 테스트 - 성공

테스트들이 다 성공한다면, 테스트에 대한 정보들을 잘 제공하는 모습이다.

이번 리팩토링을 통해 GitHub Actions를 통한 CI 구축 과정을 보다 효율적으로 관리할 수 있게 되었다. 또한, SonarCloud를 적용하여 정적 코드 분석을 자동화함으로써 코드 품질 관리를 강화할 수 있었다.

2-3. Cash 결과

각각에 Test에 대해서 Cash 파일을 관리하는 모습이다.

3. 마무리

GitHub Actions를 통해 CI 구축 과정을 병렬 처리로 전환하며, 순차적인 실행에서 발생한 문제들을 해결했다. 이러한 전환은 작업 시간을 줄이고, 서로 다른 작업 간의 의존성 문제를 없앴다. 해당 기능을 통해 문제점을 더욱 더 정확히 파악할 수 있을 것으로 기대한다.

profile
노를 젓다 보면 언젠가는 물이 들어오겠지.
post-custom-banner

0개의 댓글