본문 바로가기
안드로이드

[Android] GitHub Actions를 이용하여 자동으로 PR 테스트하기

by algosketch 2023. 11. 15.

테스트 자동화를 공부하게 된 동기

Pull Request

위 사진은 PR 페이지 하단에서 확인할 수 있는 단위 테스트 결과이다. 드로이드 나이츠 2023 앱에서 이런식으로 PR에 대해 테스트를 자동화하는 것을 보고 "멋지다"고 생각했다. 이전에도 한 번 따라해 보려 했으나, 테스트 결과의 성공 실패 여부만 나오고 위와 같이 결과 보고서를 만들어주지는 않았다.

내적인 이유는 멋지다였고, 표면상의 이유는 개발 주기에 테스트를 포함함으로써 서비스의 퀄리티를 높이는 것이다. 요즘 단위 테스트에 대해 공부하고 있기도 하니, 이번에 진행중인 프로젝트에서 단위 테스트로 멋지게 작성할 예정이다.

 

GitHub Actions를 이용한 테스트 자동화

이 글에서는 위 사진처럼 GitHub actions를 활용하기 위한 방법에 대해 살펴볼 예정이다. 우선 결과 코드를 먼저 확인해 보자. 다음 코드는 드로이드 나이츠의 workflow를 분석하여 내 프로젝트에 맞게 변경한 코드이다. 드로이드 나이츠에서는 여러 개의 workflow를 돌려서 메인이 되는 workflow에서 테스트 결과를 업로드하고, 다른 workflow에서 결과를 다운로드하여 report를 PR에 남기는 형식이다. 아래 코드는 하나의 workflow에서 모두 처리하여 조금 더 간단하게 작성해 봤다.

 

파일 형식은 yaml로 되어 있다. 만약 yaml을 처음 접한다면, xml이나 json처럼 데이터를 표현하기 위한 방식이라고 생각하면 된다. GitHub Actions를 이용하려면 반드시 .github/workflows 디렉토리에 두어야 하고, yml 또는 yaml 확장자를 사용할 수 있다.

 

name

name은 GitHub에서 보여줄 workflow의 이름이다.

name: Android Pull Request CI

 

on

자동으로 workflow를 트리거하기 위한 조건을 지정하기 위해 on을 사용한다. 트리거로 사용 가능한 이벤트는 이 문서에서 확인할 수 있다. single event, multi event 모두 설정 가능하다. main 브랜치에 PR시 트리거하려면 다음과 같이 작성한다.

on:
  pull_request:
    branches: [ main ]
on:
  push:
    branches:
      - main
      - 'releases/**'

types를 이용하면 더 정교하게 컨트롤할 수 있다. 아래 코드는 이슈가 열리거나 라벨이 달리는 이벤트를 추적한다.

on:
  issues:
    types:
      - opened
      - labeled

특정 workflow도 추적이 가능하다.

on:
  workflow_run:
    workflows: ["Build"]
    types: [requested]
    branches:
      - 'releases/**'

 

내 프로젝트의 경우 안드로이드와 서버가 같은 리포지토리를 사용하며 디렉토리로 구분하고 있으므로 android 디렉토리가 수정되었을 경우에만 트리거 되어야 한다. 따라서 프로젝트에서는 아래와 같이 작성했다.

on:
  pull_request:
    branches: [ develop ]
    paths:
      - 'android/**'

 

defaults

마찬가지로 루트가 아닌 android 디렉토리에서 실행해야 정상적으로 동작한다. 따라서 아래와 같이 작업을 실행할 디렉토리를 변경해 준다. step에서도 지정할 수 있지만 defaults에서 지정할 경우 개별적으로 지정하지 않아도 된다.

defaults:
  run:
    working-directory: ./android

 

jobs

workflow는 한 개 이상의 job을 실행하고, 기본적으로 병렬적으로 실행된다. 각 job은 runs-on에서 명시한 runner에서 실행된다.

jobs:
  unit_test:
    runs-on: ubuntu-latest

unit_test는 GitHub에 보여줄 각 job의 이름이다.

 

runs-on

job이 작동할 머신을 정의하기 위해 runs-on을 사용한다. GitHub-hosted runner, larger runner, self-hosted runner 이렇게 세 가지 종류가 있고, 앞의 두 개는 GitHub에서 제공한다. larger runner가 더 많은 자원이 할당되어 있다.

 

permissions

GITHUB_TOKEN에 부여된 기본 권한을 변경하기 위해 permissions를 설정할 수 있다. 각 job마다 설정할 수도 있고, workflow의 모든 job에 적용할 수도 있다.

permissions:
  checks: write

 

steps

job은 순서대로 실행되는 steps를 갖는다. step은 action 또는 command를 실행하거나 task를 설정할 수 있다. 각각의 step은 workspace의 파일 시스템에 접근한다. steps는 각각 자체 프로세스에서 실행되기 때문에 steps 사이에 환경 변수가 유지되지 않는다.

name은 GitHub에 보여주기 위한 각 step의 이름이다.

uses를 통해 step으로 실행할 action을 선택할 수 있다. action은 재사용 가능한 코드 조각이고, 같은 repository, public repository, 공개된 도커 이미지에 정의된 action을 사용할 수 있다. 공개된 action은 업데이트에 따라 깨질 수 있기 때문에 action을 사용할 때는 버전 명시를 권고한다. 공개된 action은 {owner/{repo}@{ref}와 같이 사용한다. (ref는 브랜치나 sha) 같은 repository라면 ./.github/actions/hello-world-action 과 같이 사용할 수 있다. (파일 경로가 다음과 같을 때 .github/actions/hello-world-action/actions.yml )

특정 action은 반드시 with와 함께 사용해야 한다.

steps:
  - name: Checkout the code
    uses: actions/checkout@v3

  - name: set up JDK 17
    uses: actions/setup-java@v3
    with:
      distribution: 'corretto'
      java-version: '17'

 

run은 OS shell을 사용하여 command-line을 실행한다. (run 키워드는 각각 새로운 프로세스를 나타내기 때문에 같은 프로세스에서 실행하려면 하나의 run에 여러 명령어를 입력해야 한다.)

- name: Grant execute permission for gradlew
  run: chmod +x gradlew

특정 조건에서만 실행시키기 위해 if 표현식을 사용할 수 있다. 원래는 if: ${{ github.event_name == 'pull_request' && github.event.action == 'unassigned' }}와 같이 ${{ }} 와 함께 사용해야 하지만 특정 조건에서 생략 가능하다. 실패한 단위 테스트가 있다면 다음 step을 진행하지 않으므로 test report를 항상 업로드 하기 위해 다음과 같이 작성했다.

- name: Publish Test Results
  if: always()
  uses: EnricoMi/publish-unit-test-result-action@v2
  with:
    files: "**/test-results/**/*.xml"

참고로 ./gradlew testDebugUnitTest 명령어로 단위테스트를 진행할 경우 윈도우 기준 app\build\test-results\testDebugUnitTest 디렉토리에 테스트 결과가 생성된다. 

 

결론

CI를 테스트하기 위해 yml을 한 번 수정할 때마다 많은 시간이 소요된다. 다른 사람이 만든 yml 파일이 내 프로젝트에서도 잘 동작하면 좋겠지만, 만약 동작하지 않는다면 이를 수정하기 위해 많은 시간이 필요할 수 있다. 따라서 나는 드로이드 나이츠 앱을 분석하면서 yml에 작성된 모든 라인을 GitHub Actions 공식문서와 함께 해석했다. 또한 여기서 사용한 action이 어떤 역할을 하는지 확인하기 위해 각 action의 README도 확인했다.

다른 프로젝트의 yml을 복사하여 사용하더라도 상황에 맞게 변경할 필요가 있으니 각 라인이 무엇을 의미하는지 확인해야 한다. 이 글에서는 내 프로젝트 yml에 사용한 문법에 대해 간단히 소개했지만, 이게 GitHub Actions에 대한 충분한 설명이 되었다고 생각하진 않는다.  공식문서 링크는 글 하단의 참고 자료 부분에 남겨두었으니, GitHub Actions 적용을 고려하고 있다면 반드시 공식문서를 읽어보기를 권한다.

 

실제 적용 결과는 아래 리포지토리에서 확인할 수 있다.

https://github.com/boostcampwm2023/and04-catchy-tape

 

GitHub - boostcampwm2023/and04-catchy-tape: 너의 목소리가 들려 ~ 📼

너의 목소리가 들려 ~ 📼. Contribute to boostcampwm2023/and04-catchy-tape development by creating an account on GitHub.

github.com

 

 

참고자료

https://github.com/droidknights/DroidKnights2023_App

 

GitHub - droidknights/DroidKnights2023_App: 국내 최대 규모 안드로이드 컨퍼런스 드로이드나이츠 2023 앱

국내 최대 규모 안드로이드 컨퍼런스 드로이드나이츠 2023 앱. Contribute to droidknights/DroidKnights2023_App development by creating an account on GitHub.

github.com

https://docs.github.com/ko/actions/using-workflows/workflow-syntax-for-github-actions

 

GitHub Actions에 대한 워크플로 구문 - GitHub Docs

워크플로는 하나 이상의 작업으로 구성된 구성 가능한 자동화된 프로세스입니다. 워크플로 구성을 정의하려면 YAML 파일을 만들어야 합니다.

docs.github.com

https://docs.github.com/ko/actions/using-workflows/triggering-a-workflow

 

워크플로 트리거 - GitHub Docs

GitHub Actions 워크플로를 자동으로 트리거하는 방법

docs.github.com

https://github.com/EnricoMi/publish-unit-test-result-action

 

GitHub - EnricoMi/publish-unit-test-result-action: GitHub Action to publish unit test results on GitHub

GitHub Action to publish unit test results on GitHub - GitHub - EnricoMi/publish-unit-test-result-action: GitHub Action to publish unit test results on GitHub

github.com