Skip to content

Github Action을 이용한 CI 테스트 자동화

yeonLog edited this page Oct 21, 2022 · 5 revisions

개요

  • CI, 테스트 자동화를 위한 설정입니다.
  • Github과 라이브러리가 제공하는 기능을 사용합니다.
  • 특정 이벤트가 발생했을 때, 어떠한 동작을 수행하라 라는 식으로 명시해두기만 하면 됩니다.





흐름

  • .github/workflows/파일명.yml 파일을 추가합니다
  • 해당 파일에 있는 설정에 따라 특정 이벤트에 정해진 job을 수행합니다





적용한 코드

name: 줍줍 백엔드 CI 테스트 자동화

on:
  push:
    branches:
      - main
      - release/*
      - develop
    paths: 'backend/**'
  pull_request:
    branches:
      - main
      - release/*
      - develop
    paths: 'backend/**'

defaults:
  run:
    working-directory: backend

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
      - name: 레포지토리를 가져옵니다
        uses: actions/checkout@v3
        with:
          token: ${{ secrets.SUBMODULE_TOKEN }}
          submodules: recursive

      - name: JDK 11을 설치합니다
        uses: actions/setup-java@v3
        with:
          java-version: '11'
          distribution: 'temurin'

      - name: Gradle 명령 실행을 위한 권한을 부여합니다
        run: chmod +x gradlew

      - name: Gradle build를 수행합니다
        run: ./gradlew build

      - name: 테스트 결과를 PR에 코멘트로 등록합니다
        uses: EnricoMi/publish-unit-test-result-action@v1
        if: always()
        with:
          files: '**/build/test-results/test/TEST-*.xml'

      - name: 테스트 실패 시, 실패한 코드 라인에 Check 코멘트를 등록합니다
        uses: mikepenz/action-junit-report@v3
        if: always()
        with:
          report_paths: '**/build/test-results/test/TEST-*.xml'
          token: ${{ github.token }}

      - name: build 실패 시 Slack으로 알립니다
        uses: 8398a7/action-slack@v3
        with:
          status: ${{ job.status }}
          author_name: 백엔드 빌드 실패 알림
          fields: repo, message, commit, author, action, eventName, ref, workflow, job, took
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
        if: failure()





적용할 이벤트 설정하기

on:
  push:
    branches:
      - main
      - release/*
      - develop
    paths: 'backend/**'
  pull_request:
    branches:
      - main
      - release/*
      - develop
    paths: 'backend/**'

두 가지 경우에 대해 동작을 수행하라고 작성해주었습니다. push와 pull_request 인데요, push는 merge를 포함하고, pull_request는 생성과 추가 push를 포함합니다.

적용 대상 A

  1. 푸시 이벤트가 발생했고
  2. 대상 브랜치 이름이 main 이거나, release/로 시작하거나, develop이고
  3. backend 이하 경로에 변경이 있었을 때

적용 대상 B

  1. PR 이벤트가 발생했고
  2. 대상 브랜치 이름이 main 이거나, release/로 시작하거나, develop이고
  3. backend 이하 경로에 변경이 있었을 때

두 이벤트 중 하나라도 발생하면 아래에 작성될 동작을 수행하는 것입니다.





기본 작업 경로 설정

defaults:
  run:
    working-directory: backend

더 아래에서 명시될 수행 작업들의 기본 경로를 설정해줍니다. 현재 줍줍의 레포지토리는 루트에 backend, frontend로 구분이되어있기 때문에, 전체 레포지토리를 clone 해올 경우, backend 경로로 들어가야 gradle build 명령을 수행할 수 있기 때문입니다.

만약 더욱 복잡한 작업이 늘어나게 될 경우, 수행할 작업마다 경로가 달라질 수도 있습니다만 현재는 불필요하게 같은 경로 선언이 반복되기 보다는 기본 설정을 잡아주는 쪽을 선택해봤씁니다.





레포지토리 Check out

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
      - name: 레포지토리를 가져옵니다
        uses: actions/checkout@v3
        with:
          token: ${{ secrets.SUBMODULE_TOKEN }}
          submodules: recursive

jobs 부터 작업들을 선언해주면 됩니다. build 는 Github에 표기될 작업의 이름이니 본인이 편한 다른 이름을 사용하셔도 됩니다.

jobs 내에는 여러개의 작업들이 들어올 수 있고, 하나의 작업은 steps 에 명시된 내용을 순차적으로 수행하는 식입니다. 이번 프로젝트에 적용한 내용은 하나의 build 라는 이름의 작업에 여러 개의 step을 선언하여 적용해보았습니다.

steps 에는 순차적으로 수행할 내용들을 명시합니다.

  • name 에는 작업할 내용이 무엇인지 식별할 수 있는 값을 넣습니다. uses 에는 외부 라이브러리를 활용할 때 해당 라이브러리를 선언해주는 곳입니다. with에는 라이브러리에 대한 추가적인 옵션 또는 설정을 선언해둡니다.

위 예시는 checkout 라이브러리를 활용하는 것인데요, 팀 프로젝트의 레포지토리는 public 이지만, 내부에서 환경설정 파일을 숨기기 위해 별도 private 팀 레포지토리를 submodule로 품고 있습니다. 따라서 checkout 할 때 해당 submodule private 레포지토리도 가져올 수 있도록 토큰 설정과 submodule 설정을 with 문에 추가한 모습입니다.





Repository Secret 설정

| Secrets are environment variables that are encrypted. | Anyone with collaborator access to this repository can use these secrets for Actions.

Github Action에서 사용될 민감 정보들을 레포지토리 자체에 환경변수처럼 심어두는 설정입니다.

Repository의 Settings -> Secrets -> Actions 에 들어가면 추가할 수 있고, 수정, 삭제도 가능하지만, 수정 시 기존 설정값을 확인할 수는 없습니다. 또한 포크된 레포지토리에서는 업스트림 레포지토리에 설정된 값을 사용할 수 없습니다. 보안적으로도 훌륭하게 처리되어 있는 모습이네요.

이번 CI 테스트 자동화에서는 Slack 알림을 위한 Webhook URL, 서브모듈 checkout 을 위한 private 레포지토리에 접근 권한이 있는 토큰 이렇게 두 가지 정보를 민감 정보로 숨겨야 해서 Secret 설정을 사용했습니다.

token: ${{ secrets.SUBMODULE_TOKEN }} 와 같이 선언하면 SUBMODULE_TOKEN 에 설정해둔 값이 자동으로 주입되어 Github Actions이 수행됩니다.





빌드 테스트

- name: JDK 11을 설치합니다
  uses: actions/setup-java@v3
  with:
    java-version: '11'
    distribution: 'temurin'

- name: Gradle 명령 실행을 위한 권한을 부여합니다
  run: chmod +x gradlew

- name: Gradle build를 수행합니다
  run: ./gradlew build

레포지토리를 가져온 이후에 빌드를 진행하기 위한 step들입니다.

JDK를 설치하고, gradlew 명령어 권한을 부여하고, build를 수행합니다. gradle의 build 명령어는 다음과 같은 작업들을 순차적으로 수행합니다.

gradle build 명령어가 수행하는 내용들 따라서 build 명령어만 호출해도 충분합니다. 테스트까지 이미 포함되어 있기 때문입니다. 다만 빌드 시점에 테스트에 관련된 로그들을 모두 확인하고 싶다면 ./gradlew --info build 와 같이 선언하는 것도 방법이 될 수 있지만 아무래도 성능을 약간 양보해야 할 겁니다.





PR에 테스트 결과 코멘트로 등록하기

- name: 테스트 결과를 PR에 코멘트로 등록합니다
  uses: EnricoMi/publish-unit-test-result-action@v1
  if: always()
  with:
    files: '**/build/test-results/test/TEST-*.xml'

빌드 테스트를 수행한 뒤에 선언된 step입니다. gradle build 를 수행하고 나면 아래와 같은 경로에 테스트 결과에 대한 내용을 담은 파일이 생성되는데요, 이 파일들을 이용해 테스트 결과를 포매팅하여 PR에 코멘트 형식으로 추가해주는 라이브러리입니다.

테스트 결과를 코멘트로만 달아주는 작업은 성공 실패 여부와 상관없이 항상 동작하기를 기대해서 always 선언을 해준 모습입니다. 파일은 테스트 파일이 생성되는 경로를 명시해둔 모습입니다.




테스트 결과 코멘트는 위와 같이 작성됩니다. see this check를 클릭하면 아래와 같은 이미지가 나옵니다.


어떤 테스트 메서드가 실패했는지, 실제 로그는 어떠한지까지 볼 수 있습니다. 이 기능 덕분에 gradle build 명령어에서 --info를 굳이 선언하지 않아도 된다고 판단했습니다.





오류가 발생한 코드라인에 Check 코멘트 자동 생성하기

- name: 테스트 실패 시, 실패한 코드 라인에 Check 코멘트를 등록합니다
  uses: mikepenz/action-junit-report@v3
  if: always()
  with:
    report_paths: '**/build/test-results/test/TEST-*.xml'
    token: ${{ github.token }}

테스트 실패 케이스가 발생할 경우, 해당 실패가 발생한 코드라인에 코멘트를 자동 생성해주는 기능입니다. PR에서 File Changed 에 들어가서 바로 어느 코드라인에서 왜 실패했는지 쉽게 파악할 수 있습니다.

always로 선언하더라도, 실패할 경우만 작성됩니다. 다만 신기한 점은, 만약 해당 PR에 오류를 수정해서 추가 PUSH를 하게 되면, 새롭게 Github Actions이 동작하는데요, 이때 실패했던 테스트가 성공하게 되면 기존에 생성됐던 Check 코멘트가 자동으로 삭제됩니다. 따라서 이전에 실패했는데 지금은 고쳐지도록 반영된 거 아닌지 헷갈릴 일이 없겠네요!

설정에는 동일하게 테스트 결과 파일이 생성되는 경로를 선언해주었고, 토큰으로 선언된 github.token은 별도 secret설정된 값이 아니고 기본값이니 저대로 명시하시면 됩니다.





빌드 테스트 실패 시, Slack 알림 받기

- name: build 실패 시 Slack으로 알립니다
  uses: 8398a7/action-slack@v3
  with:
    status: ${{ job.status }}
    author_name: 백엔드 빌드 실패 알림
    fields: repo, message, commit, author, action, eventName, ref, workflow, job, took
  env:
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
  if: failure()



별도로 설정해야할 내용은 SLACK_WEBHOOK_URL을 Repository Action Secret으로 추가하는 내용입니다. ${{ job.status }} 는 별도로 설정해줄 내용 없이 그대로 명시하시면 됩니다.

failure() 로 선언하면 실패시에만, always()로 선언하면 성공 실패 여부와 상관없이 항상 알림을 보내줍니다.

위 사례는 실제 오늘 있었던 사례인데요, Github Actions가 적용되기 이전에 생성됐던 PR이었고, Github Actions가 추후 적용되었으며, 그리고 나서 PR이 merge되어 Github Actions가 main 브랜치에 push 이벤트로 인해 트리거된 상황이었습니다.

테스트 격리가 온전히 되지 않아 한 가지 테스트 케이스가 실패하고 있었고, Slack 알림 덕분에 merge된 결과 코드가 정상 동작하지 않음을 인지할 수 있었습니다. 앞으로는 PR 생성시, 코드 추가 PUSH 시 바로바로 테스트가 진행될 테니 오늘처럼 merge가 일어난 뒤에 이슈를 발견할 일은 없지 않을까 싶네요.





수행된 Github Actions 목록 확인하기


Actions 탭의 존재 자체를 이번 Github Actions를 적용하며 처음 깨달았습니다.

PR 이벤트에 적용되는 내용들은 PR 페이지에서 볼 수 있기 때문에 상관없을 수 있지만, merge 이벤트, push 이벤트 등에 대응하는 내용들은 해당하는 별도 페이지가 존재하지 않기 때문에 결과를 보기 위한 페이지가 없습니다.

따라서 레포지토리의 Actions 탭에 들어가서 수행된 내용을 확인해야 합니다.

위 이미지에서 빌드 실패한 feat: 채널명 변경 ~ 의 경우, merge 이벤트로 인해 수행되었던 테스트인데, 실패하여 x표시되어 있고, 이후 hotfix 브랜치에서 처리한 테스트 격리는 정상 완료되었기에 초록색으로 나타나네요.




Slack 연동을 위한 웹훅 주소 설정 방법

  • 알림을 받을 채널을 Slack 워크스페이스에서 생성
  • https://api.slack.com 접속
  • Your Apps 를 통해 설정하고자 하는 Slack App으로 진입
  • 좌측 메뉴 중 Incoming Webhooks 선택
  • 토클 버튼이 비활성화 되어있다면 클릭하여 활성화 처리
  • Add New Webhook to Workspace 버튼을 눌러 알림 받을 채널을 설정
  • 생성된 Webhook URL을 Github Repository 에 Secret으로 설정한 후, yml파일에서 이를 참조하도록 구성




Github Repository Secret 설정 방법






참고 자료

Clone this wiki locally