Backend/기타

GitHub Actions를 활용한 CI 구축

sangwonYoon 2023. 6. 17. 04:45

Open Domain Question Answering 프로젝트를 진행하면서 코드를 수정하여 merge할 때마다 해당 코드에 문제가 없는지 테스트를 진행해야 할 필요성을 느꼈다. train과 inference가 평균적으로 1시간 가까이 걸리기 때문에 코드의 문제가 뒤늦게 발견되는 경우 시간 낭비가 심했다. 따라서 코드가 master branch에 merge될 때마다 테스트가 자동으로 수행될 수 있도록 CI 환경을 구축하기로 결정했다.

 

요구사항

CI 환경을 구축하면서 내가 정의한 요구사항은 다음과 같다.

 

1. 사전에 코드의 문제를 파악하기 위해 다양한 가능성을 고려한 테스트를 진행한다.

테스트를 위한 파이썬의 내장 모듈인 unittest가 존재하지만, unittest는 테스트 작성을 위해 반드시 클래스를 정의해야 하는 반면, 함수만 정의하더라도 테스트를 진행할 수 있는 pytest 모듈을 사용했다. 추가적으로, pytest-xdist 플러그인을 통해 테스트를 병렬적으로 수행할 수 있다는 점도 pytest 모듈을 선택한 이유 중 하나였다.

 

2. 테스트 소요 시간을 최소화한다.

CI를 구축하는 가장 큰 이유가 문제를 빠르게 파악하여 시간을 절약하기 위함이므로, 테스트 소요 시간을 가능한 한 최소화해야 했다. 따라서 가장 시간이 많이 소요되는 train과 inference의 테스트 소요 시간을 줄이기 위해 적은 양의 데이터만을 사용했다. GitHub Actions를 통해 테스트를 진행하기 위해서는 repository에 데이터가 업로드되어 있어야 하는데(또는 인터넷 상에서 데이터를 다운로드 받거나), 프로젝트에서 사용하는 데이터는 저작권 문제로 인해 repository에 업로드 할 수 없으므로 형식을 맞춘 가짜 데이터를 제작하여 사용했다.

 

3. 테스트가 자동으로 실행된다.

프로젝트 기간 동안 코드의 수정과 merge가 자주 일어나기 때문에 테스트의 자동화가 필수적이었다. 이를 위해 GitHub Actions에 workflow를 생성하여 master branch에 코드가 push되거나 pull request될 때마다 자동으로 테스트가 진행되는 환경을 구축했다.

 

테스트 코드 작성

tests 디렉토리 밑에 test_*.py 이름을 갖는 테스트 파일, fixture를 정의한 conftest.py, 설정 파일인 pytest.ini를 작성했다.

테스트 파일 중 하나인 test_inference.py는 아래와 같이 작성했다.

import os
import sys

sys.path.append(os.path.dirname(os.path.abspath(os.path.dirname(__file__))))

from inference import main


def test_inference():

    os.environ["WANDB_DISABLED"] = "true"
    
    BASE_DIR = os.path.abspath(os.path.dirname(__file__))
    PAR_DIR = os.path.dirname(BASE_DIR)

    checkpoint_path = sorted(os.listdir(os.path.join(PAR_DIR, "checkpoint")), key=lambda x: x)[0]
    model_name = os.path.join(PAR_DIR, "checkpoint/", checkpoint_path)

    main(model_name=model_name, data_path=os.path.join(BASE_DIR, "../dummy_data"))


def test_output():
    assert os.path.isfile("../output/predictions.json")

test_inference()는 inference.py에 정의된 main() 함수의 동작을 테스트하는 함수이다. 테스트 진행 중에는 WandB를 끔으로써 테스트 내용이 로깅되지 않게 하면서 동시에 리소스 낭비를 최소화했다.

test_output()는 inference 이후 생성되어야 할 파일이 정상적으로 생성되는지 테스트하는 함수이다.

 

GitHub Actions workflow 작성

GitHub Actions는 간단하게 말해서 workflow에 정의한 내용들을 GitHub가 제공하는 서버가 실행한다고 생각하면 된다.

GitHub Actions를 사용하기 위해서는 .github/workflows 디렉토리 아래에 YAML 파일을 생성하거나, GitHub의 GUI를 통해 생성할 수 있다. 그 중에서 GUI를 통해 workflow를 생성하는 방법에 대해 간단하게 알아보자.

GitHub repository에서 Actions 탭을 누르면 다음과 같은 화면이 보일 것이다. 이 화면에서 Publish Python Package의 Congifure 버튼을 누르면 python package를 publish하기 위한 기본 workflow 파일을 제공해준다.

workflow를 바닥부터 짜고 싶다면, set up a workflow yourself를 누르면 된다.

 

나는 테스트를 위한 workflow를 생성해야 하므로 Python application을 선택했다.
내가 작성한 workflow 파일은 아래와 같다.

name: CI for ODQA

on:
  push:
    branches: [ "master" ] # master branch에 push할 때마다 실행
  pull_request:
    branches: [ "master" ] # master branch에 pull request할 때마다 실행

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3
    
    - name: Set up Python 3.10
      uses: actions/setup-python@v3
      with:
        python-version: "3.10"
        
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        bash ./install/install_requirements.sh
        
    - name: Test train.py
      working-directory: ./tests
      run: pytest test_train.py
      
    - name: Test inference.py
      working-directory: ./tests
      run: pytest test_inference.py --checkpoint checkpoint/checkpoint-10

 

코드를 나눠서 자세하게 살펴보자.

name: CI for ODQA

on:
  push:
    branches: [ "master" ] # master branch에 push할 때마다 실행
  pull_request:
    branches: [ "master" ] # master branch에 pull request할 때마다 실행
  • name: workflow의 이름을 부여한다.
  • on: workflow의 실행 조건을 설정한다.

 

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3
    
    - name: Set up Python 3.10
      uses: actions/setup-python@v3
      with:
        python-version: "3.10" # 파이썬 버전 3.10 사용
        
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        bash ./install/install_requirements.sh
        
    - name: Test train.py
      working-directory: ./tests
      run: pytest test_train.py
      
    - name: Test inference.py
      working-directory: ./tests
      run: pytest test_inference.py --checkpoint checkpoint/checkpoint-10
  • jobs: workflow에서 실행할 작업들을 하위에 명시한다. 위 workflow에는 build라는 이름의 작업 하나만 존재한다.
  • runs-on: 해당 작업이 어느 환경에서 수행될 지 설정한다.
  • steps: 작업이 수행할 내용(step)들을 하위에 정의한다.
  • uses: 미리 만들어진 action을 가져와 사용할 수 있다. 예를 들어, actions/checkout@v3는 해당 repository의 코드를 GitHub Actions 서버로 내려받을 때 사용한다.
  • name: step의 이름을 부여한다.
  • run: 운영체제의 shell에서 command-line 명령어를 실행한다.
  • working-directory: command-line 명령어를 실행할 working directory를 지정한다.

 

위와 같은 형식으로 YAML 파일을 repository에 올리게 되면, CI 환경 구축이 완료된다.

 

CI 파이프라인 테스트

이제 정상적으로 CI 파이프라인이 동작하는지 확인해보자.

위와 같이 master 브랜치에 PR을 하게 되면 아래와 같이 자동으로 CI가 진행된다.

 

Actions 탭에서 현재 진행중인 workflow에 들어가면 진행중인 작업에 대한 로그를 확인할 수 있다.

 

실패한 workflow에 대한 로그를 확인하면서 코드의 어느 부분에 문제가 있는지 확인할 수 있다.

 

CI가 성공적으로 완료되면, 아래와 같이 화면이 바뀌는 것을 확인할 수 있다.