캡스톤프로젝트:: Github Actions을 이용한 CI - 빌드 및 테스트 자동화

류영준·2022년 12월 30일
2
post-thumbnail

개요

캡스톤 팀 프로젝트 백엔드 팀원은 2명이다. 이처럼 한 프로젝트를 여러 사람이 개발할 때 코드의 안정성은 굉장히 중요하다.

환경 변수 설정, 비즈니스 로직 구성, Git 충돌 등 여러 사람들이 코드를 공유하면서 발생할 수 있는 문제점은 매우 많다.

실수를 방지하기 위해서 테스트 코드를 작성하지만 매 PR 코드리뷰 때마다 각 리뷰어들이 일일히 테스트 코드를 돌려보면서 리뷰하면 생산성이 저하된다.

단순히 PR의 테스트가 잘 돌아가고 문제가 없다라는 코멘트보다는 테스트의 성공을 보장하고 테스트 커버리지를 측정할 수 있는 수단이 필요하다.

그래서 CI라는 개념과 함께 빌드 및 테스트 자동화를 도입해보기로 했다.

CI

CI를 단편적으로 설명하기보다는 CI/CD 개념을 같이 설명하는 것이 더 도움이 될 거 같다.

CI/CD는 애플리케이션 개발 단계를 자동화하여 애플리케이션을 보다 짧은 주기로 고객에게 제공하는 방법이다.

여기서 지속적인 통합을 나타내는 CI(Continuous Integration)는 애플리케이션을 빌드, 테스트하여 이상이 없는 경우 소스코드를 레포지토리 병합하는 과정을 자동화하는 것을 의미한다.

즉, 여러 개발자들이 하나의 프로젝트를 같이 개발할 때 발생하는 불일치를 최소화해주고 공유하는 코드의 신뢰성을 높이는 개념이다.

Github Actions

Github Actions을 선택한 이유

CI/CD 툴로는 설치형 CI/CD 툴인 Jenkins, 클라우드형 CI/CD 툴인 Travis CI 등이 있는데, 이것들을 사용하지 않은 이유는 다음과 같다.

현재 huemap 프로젝트는 Github에서 제공해주는 기능들을 사용하며 Project Management를 하고있다.

Issue를 발행하고, Projects를 통해 전체적인 프로세스 일정을 관리하며, Github 하나에서 전부 관리하고 있다.

이처럼 Github 저장소를 기반으로 하나로 통일된 환경에서 CI 수행이 가능하면 좋다고 생각하여 채택하게 되었다.

Github Actions란?

Github Actions은 간단하게 말하면 Github에서 제공하는 CI/CD 툴이다.

build, test, deploy 등 필요한 Workflow를 등록해두면 Gihtub의 특정 이벤트 push, pull request가 발생했을 때 해당 워크 플로우를 수행한다.

예를 들어 Pull Request를 올리면 자동으로 해당 코드의 테스트를 수행하여 수행한다던지 main branch에 코드를 push 하면 자동으로 코드를 배포하는 등 여러 가지 반복적인 작업을 자동으로 수행해준다.

추가로 생활코딩의 영상도 참고하여 공부하였다.

Github Actions 자동 빌드 및 테스트

Github Actions 를 사용한 CI 환경을 구축해보자.

PR를 올렸을 때 자동으로 빌드 및 테스트를 수행한다.

Workflow 생성 및 설정 - yml 파일 작성

.github/workflows/backend-ci.yml

name: backend-ci

on:
  push:
    branches:
      - 'main'
  pull_request:
    branches:
      - 'main'

jobs:
  code-coverage:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3
      - name: Set up JDK 11
        uses: actions/setup-java@v3
        with:
          java-version: '11'
          distribution: 'temurin'

      - name: Grant execute permission for gradlew
        working-directory: ./backend
        run: chmod +x gradlew

      - name: Test with Gradle
        working-directory: ./backend
        run: ./gradlew build
          
      - name: Publish Unit Test Results
        uses: EnricoMi/publish-unit-test-result-action@v1
        if: ${{ always() }}
        with:
          files: backend/build/test-results/**/*.xml
  • Event(on): 설정한 조건이 발생하면 워크플로우를 실행한다. (ex. push, pull request, commmit 등)
    • 현재 main 브랜치에 push 하거나 commit 하는 경우에 대해 동작을 수행하라고 작성했다.
  • Runner(runs-on): 워크플로우를 실행하는 운영체제 환경, Github에서 호스팅하는 러너를 사용할 수 있다.
  • Step(uses, run): commands 또는 actions를 실행할 수 있는 태스크, actions을 사용할 때는 uses를 사용하고, commands를 사용할 때는 run을 사용한다.
  • working-directory : 한 레포지토리 안에 backend, frontend 등이 있으므로 수행 대상 디렉토리를 설정
  • EnricoMi/publish-unit-test-result-action@v1 : PR에 테스트 결과 코멘트로 등록하기
    • 빌드 테스트를 수행한 뒤에 선언된 step
    • gradle build을 수행하고 나면 선언한 경로에 테스트 결과에 대한 내용을 담은 파일이 생기는데, 이 파일들을 이용해서 테스트 결과를 포맷팅하여 PR에 코멘트 형식으로 추가해주는 라이브러리이다.
    • 테스트 결과를 코멘트로 등록하는 작업은 성공 실패 여부에 상관없이 항상 동작하기를 기대해서 always로 선언을 해주었다.


자동 생성된 테스트 결과 코멘트

현재 작성한 워크플로우 흐름은 다음과 같다.

  1. main 브랜치에 push 혹은 pull request가 발생하면 ubuntu 환경에서 워크플로우 실행
  2. Job이 repository에 접근할 수 있도록 actions/checkout@v3 액션 사용
  3. JDK 11 사용
  4. gradle에 실행 권한 부여
  5. gradle을 이용하여 빌드 수행

Github 설정

  • CI가 실행중이거나 실패한 경우 merge가 되면 안되기 때문에 github에서 제공하는 설정을 이용한다.
  • 적용을 하게 되면 워크플로우가 실행중이거나 실패한 경우 merge가 불가능하다.
  • 추가로 merge 하기 전에 PR이 필수적이여야하는 설정도 추가한다.
    • Require a pull request before merging

커버리지 측정

  • 테스트 커버리지 측정 도구 jacoco
  • 테스트 커버리지 관리는 codecov
    • 매번 단위테스트가 현재 프로젝트에 대해 얼마나 커버를 하는지 메서드 line 단위로 확인을 하고 싶어 code coverage에 대해 알아보게 되었다. 그 중에서도 다른 레퍼런스에 비해 많이 검색되고, 자체 docs를 잘 관리하고 있는 Codecov 를 최종적으로 선택하게 되었다. 그리고 PR에 보기 편하게 report를 해주고, Github Actions와 쉽게 연동할 수 있도록 기능을 제공하고 있기 때문이다.

build.gradle에 jacoco 의존성 추가

plugins {
		...
    id 'jacoco'
}

jacoco {
    toolVersion = "0.8.8"
}

tasks.named('test') {
	...
	finalizedBy jacocoTestReport
}

jacocoTestReport {
	executionData(fileTree(project.rootDir.absolutePath).include("**/build/jacoco/*.exec"))

	reports {
		html.enabled true
		xml.enabled true
		csv.enabled false
	}
}

플러그인에 jacoco 를 추가해주고, junit test가 수행될 때 같이 돌 수 있도록 finalizedBy를 넣어준다. 

jacocoTestReport는 바이너리 커버리지 결과를 사람이 읽기 좋은 형태의 리포트로 저장한다. html 파일로 생성해 사람이 쉽게 눈으로 확인할 수도 있고, SonarQube 등으로 연동하기 위해 xml, csv 같은 형태로도 리포트를 생성할 수 있습니다. 프로젝트에서 csv 파일은 사용하지 않을 것 같아 커스텀하게 false로 지정하고, 나머지는 Codecov에 전송할 때 필요한 파일들의 확장자이기 때문에 true로 설정해주었다.

  • build.gradle 예시
    plugins {
    	id 'org.springframework.boot' version '2.7.4'
    	id 'io.spring.dependency-management' version '1.0.14.RELEASE'
    	id 'java'
    	id 'jacoco'
    }
    
    group = 'com.huemap'
    version = '0.0.1-SNAPSHOT'
    sourceCompatibility = '11'
    
    repositories {
    	mavenCentral()
    }
    
    jacoco {
    	toolVersion = '0.8.8'
    }
    
    dependencies {
    	implementation 'org.springframework.boot:spring-boot-starter-web'
    	testImplementation 'org.springframework.boot:spring-boot-starter-test'
    }
    
    tasks.named('test') {
    	useJUnitPlatform()
    	finalizedBy jacocoTestReport
    }
    
    jacocoTestReport {
    	executionData(fileTree(project.rootDir.absolutePath).include("**/build/jacoco/*.exec"))
    
    	reports {
    		html.enabled true
    		xml.enabled true
    		csv.enabled false
    	}
    }

codecov.yml 생성

codecov:
  require_ci_to_pass: yes

comment:
  layout: "reach,diff,flags,files,footer"
  behavior: default
  require_changes: false
  branches:
    - main

해당 파일은, Codecov에서만 읽어가는 파일이다.

꼭 필요한 파일은 아니지만 커스텀하게 지정하고싶은게 있다면 반드시 생성해야하는 파일이다.

우리는 PR에 report를 날려주어야 하기 때문에 해당 파일로 조작이 필요했다.

그래서 다른 기능은 넣지 않았고 간단히 CI 통과여부와 PR comments 를 넣어 주었습니다.

그리고 해당 파일은 프로젝트 root 디렉토리에 추가해주어야한다.

Workflow yml 파일 작성

marketplace에 등록되어있는 Codecov의 action을 사용한다.

기존에 작성해두었던 Workflow yml에 위 코드를 추가한다.

테스트를 돌리면 jacoco가 생성한 report를 codecov에 업로드한다.

file 은 jacoco가 생성한 report의 경로이며, ./build/reports/jacoco/test/jacocoTestReport.xml는 jacoco의 기본 설정이다.

  • backend-ci.yml (CI Workflow) 예시
    name: backend-ci
    
    on:
      push:
        branches:
          - 'main'
      pull_request:
        branches:
          - 'main'
    
    jobs:
      code-coverage:
        runs-on: ubuntu-latest
    
        steps:
          - uses: actions/checkout@v3
          - name: Set up JDK 11
            uses: actions/setup-java@v3
            with:
              java-version: '11'
              distribution: 'temurin'
    
          - name: Grant execute permission for gradlew
            working-directory: ./backend
            run: chmod +x gradlew
    
          - name: Test with Gradle
            working-directory: ./backend
            run: ./gradlew build jacocoTestReport
    
          - name: Codecov
              uses: codecov/codecov-action@v3.1.0
              with:
                token: ${{ secrets.CODECOV_TOKEN }}
                file: ./backend/build/reports/jacoco/test/jacocoTestReport.xml
              
          - name: Publish Unit Test Results
            uses: EnricoMi/publish-unit-test-result-action@v1
            if: ${{ always() }}
            with:
              files: backend/build/test-results/**/*.xml

codecov 등록

codecov로 들어가서 github으로 회원가입 후 repository를 등록하고, 발급된 토큰을 Github Actions이 수행될 때 환경변수 값으로 주입될 수 있도록 Github secrets에 등록한다.

CODECOV_TOKEN=912I31235-6221-1024-4723-9d998281jd82(예시)에서 ‘=’을 기준으로 뒷 부분만 등록해야한다.

Workflow 실행

PR을 올려서 workflow을 실행해보면 위와 같은 결과가 나온다.

codecov 레포트가 코멘트로 잘 달렸고, codecov 내에서도 잘 올라간 것을 확인할 수 있다.

아직 데이터가 충분하지 않아서 전체 코드커버리지가 잘 측정되지 않는다.

결론

Github과 연계성이 좋은 Github Actions을 활용해서 CI를 구축하였다. 코드 리뷰를 하는 사람은 직접 코드를 돌려보지 않아도 빌드와 테스트가 성공한다는 사실을 알 수 있었고 코드커버리지까지 확인할 수 있었다.

최종적으로 70.09%의 테스트 커버리지를 달성하였다. 아쉬웠던 점은 단위 테스트를 하다보니 실수로 누락된 테스트들이 있었다. 그래서 코드 커버리지가 일정 수준을 넘지 못하면 merge가 불가능하게 만들어서 테스트 코드 작성을 강제할 수도 있다는데, 추후에 적용할 수 있으면 적용해볼 예정이다.

profile
Backend Developer

0개의 댓글

관련 채용 정보