과제/개인과제

원티드 프리온보딩 인턴십 사전과제(1) - GitHub Actions를 활용한 CI 테스트 자동화

마이구미+ 2023. 8. 7. 14:58

<GitHub Actions란?/>

  • GitHub에서 제공하는 CI/CD 및 자동화 도구
  • 프로젝트에 대한 작업 워크플로우를 자동화하고 소프트웨어 개발 과정에서 발생하는 일련의 과정을 클라우드 기반의 실행 환경에서 처리할 수 있도록 함

<CI는 무엇인가?/>

  • Continuous Integration의 약자
  • 소프트웨어 개발에서 소스 코드 변경 사항을 지속적으로 합치고(통합) 빌드, 테스트하는 개발 방법론
  • 코드 통합을 자주 진행함으로써 통합으로 인한 문제를 가능한 한 빨리 발견하고 해결하는 것을 목적으로 함
  • CI를 적용할 때의 장점
    • 최신 코드 상태를 유지하며 협업 시 문제를 빠르게 공유할 수 있음
    • 자동화된 테스트를 통해 코드 품질을 향상시키고 신규 기능의 안정성을 확보함
    • 개발, 테스트, 배포 과정에서의 에러를 줄일 수 있음
  • CI를 구현하고 관리하는 도구들
    • GitHub Actions
    • Jenkins
    • Travis CI

<프로젝트 소개/>

 

GitHub - lordmyshepherd-edu/wanted-pre-onboardung-backend-selection-assignment: Wanted Pre-Onboarding Backend Internship Selecti

Wanted Pre-Onboarding Backend Internship Selection Assignment - GitHub - lordmyshepherd-edu/wanted-pre-onboardung-backend-selection-assignment: Wanted Pre-Onboarding Backend Internship Selection As...

github.com

  • 과제 설명 중에 [통합 테스트 또는 단위 테스트 코드를 추가한 경우]에 가산점을 준다고 되어 있다
  • 팀프로젝트 할 때 CI를 적용해서 테스트 자동화를 하고 싶었는데 시간이 촉박하고 여유가 없어서 구현하지 못했다
  • 이번 기회에서 테스트 자동화를 해보자 싶어서 도전했다
  • 먼저 users 앱과 articles 앱에 대한 테스트 코드를 작성해야 한다!
  • 전반적으로 성공/실패 테스트를 나눠서 작성했다
  • users 앱 테스트코드 간략 설명
    • 회원가입 성공: 이메일 형식을 지키고, 비밀번호가 8자 이상인 경우 회원가입에 성공하며 HTTP status는 201 created가 뜬다
    • 회원가입 실패: 이메일 형식을 지키지 않았거나 비밀번호가 8자 이하로 입력하면 회원가입에 실패하며 HTTP status는 400 bad request가 뜬다
    • 로그인 성공: DB에 저장된 이메일과 비밀번호를 맞게 입력하면 로그인에 성공하며 HTTP status는 200 ok가 뜬다
    • 로그인 실패-이메일: DB에 존재하지 않는 이메일 입력하는 경우 존재하지 않는 이메일이라는 메시지를 반환한다. 비밀번호를 맞게 작성해도 이메일이 DB에 존재하지 않으면 HTTP status 400 bad request와 함께 해당 메시지만 반환된다
    • 로그인 실패-비밀번호: DB에 존재하는 이메일을 입력하였으나 비밀번호가 틀린 경우 비밀번호를 다시 확인해 달라는 메시지를 반환하며, HTTP staus 400 bad request가 뜬다
  • articles 앱 테스트코드 간략 설명
    • 게시글에 이미지를 선택사항으로 업로드 할 수 있으므로, 이미지 생성 함수를 구현했다
    • 게시글 CURD 테스트는 마찬가지로 성공/실패 경우로 나눠서 구현했다
    • setUpTestData 메서드에 user 데이터를 정의한 후, user 객체를 생성했고, article 데이터를 정의했다
    • setUp 메서드에선 setUpTestData에서 생성한 user 객체를 활용해서 access_token을 받아 로그인 하는 과정을 담고, article 객체를 생성했다
    • 게시글 성공 테스트는 이미지 없는 게시글과 있는 게시글 2개로 나눠서 테스트 코드 작성했다
    • 게시글 실패 테스트는 비로그인 한 경우, 제목 미입력, 내용 미입력 3개로 나눠서 테스트 코드 작성했다
    • 게시글 목록 조회 테스트는 article 객체를 4개 더 생성해서 총 게시글 5개가 맞게 나오는지 테스트 했다
    • 특정 게시글 조회 테스트는 url에 article_id를 넣어서 테스트 했고, 조회에 성공하면 HTTP status 200 ok를 반환한다
    • 게시글 수정 테스트는 성공/실패로 나눴고, 작성자가 아닌 사용자가 수정 시도 시 HTTP status 403 forbidden을 띄우며 수정 실패한다
    • 게시글 삭제 테스트도 수정 테스트와 마찬가지다

<테스트 자동화하는 방법/>

- 사전 준비 단계

  • 레파지토리 생성 후에 Actions 탭을 누르면 Workflow를 생성하는 버튼이 있다

  • 아마 내 repo에는 이미 workflow가 있어서 New workflow를 눌러야 새 워크플로우를 생성할 수 있는데 처음 Actions를 눌렀다면 바로 생성할 수 있게끔 되어 있을 거다

  • 나는 장고 프로젝트 중이므로 Django를 눌렀다

  • 그럼 친절하게도 기본적인 틀이 작성된 코드가 나온다
  • 이걸 프로젝트에 맞게 커스텀 해서 오른쪽 위에 Commit changes라는 초록 버튼을 누르면 된다
name: Django CI	# 이 워크플로우의 이름을 설정함

on:	# 언제 워크플로우를 실행할지 설정함
  push:	# 브랜치에 push 이벤트가 발생하면 실행됨
    branches: [ "main" ]	# main 브랜치에서 발생한 push 이벤트에만 실행됨
  pull_request:	# Pull Request 이벤트가 발생하면 실행됨
    branches: [ "main" ]	# main 브랜치에 대한 PR 이벤트에만 실행됨

jobs:	# 워크플로우 내에서 실행되는 일련의 작업들을 정의함
  build:	# 작업의 이름을 설정함. 이 경우 작업 이름은 "build"임

    runs-on: ubuntu-latest	# 작업이 실행되는 가상 환경을 설정함. 이 경우 Ubuntu 최신 버전에서 실행됨
    strategy:	# 병렬 작업을 실행하는 방법 구성
      max-parallel: 4	# 최대 4개의 병렬 작업을 실행함
      matrix:	# 작업 내에서 사용하는 변수들의 조합을 정의함
        python-version: [3.7, 3.8, 3.9]	# Python 3.7 3.8 3.9  버전을 사용함

    steps:	# 작업을 구성하는 순차적 단계를 정의함
    - uses: actions/checkout@v3	# GitHub 저장소의 소스 코드를 가상 환경에 가져오는 데 사용되는 actions/checkout 액션을 사용함. @v3은 이 액션의 버전
    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v3
      with:	# 20~23번째 줄: Python 버전 설정을 위해 actions/setup-python 액션을 사용함
        python-version: ${{ matrix.python-version }}	# 각 python 버전에 해당하는 가상환경 설정
    - name: Install Dependencies
      run: |	# 24~27번째 줄: 프로젝트에 필요한 종속성 설치
        python -m pip install --upgrade pip	# pip 업그레이드
        pip install -r requirements.txt	# requirements.txt 파일에 명시된 패키지 설치
    - name: Run Tests
      run: |	# 28~30번째 줄: Django 프로젝트 내에 정의된 테스트 코드 실행
        python manage.py test
  • 기본으로 제공된 코드에 설명을 붙여보았다

- yaml 파일 커스터마이징

name: Preonboarding CI	# 이름 변경

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

jobs:
  build:

    runs-on: ubuntu-latest
    strategy:
      max-parallel: 4
      matrix:
        python-version: [3.11]	# 내 컴퓨터에 설치된 Python 버전으로 설정함

    steps:
    - uses: actions/checkout@v3
    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v3
      with:
        python-version: ${{ matrix.python-version }}

    - name: Set Environment Variables
      run: |	# Django 프로젝트 내 환경변수, GitHub 저장소 내 Secrets에 저장
        echo "DEBUG=${{ secrets.DEBUG }}" >> $GITHUB_ENV
        echo "SECRET_KEY=${{ secrets.SECRET_KEY }}" >> $GITHUB_ENV
        echo "SIGNING_KEY=${{ secrets.SIGNING_KEY }}" >> $GITHUB_ENV
        echo "RUNNING_TESTS=True" >> $GITHUB_ENV

    - name: Install Dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements.txt

    - name: Run User App Tests
      run: |	# 앱별 테스트 실행 코드 분리
        python manage.py test users
    
    - name: Run Articles App Tests
      run: |	# 앱별 테스트 실행 코드 분리
        python manage.py test articles
  • Django 프로젝트 settings.py 파일에 있는 환경변수에 대한 처리를 추가했다
  • SECRET_KEY는 Django 프로젝트 전반에 대한 암호화와 인증에 필요한 코드이고, SIGNING_KEY는 JWT 토큰의 서명 및 검증에 사용됨
  • RUNNING_TESTS는 DB 관련한 환경변수인데, DB를 mysql을 사용하고 있는데 mysql 관련 정보를 GitHub secrets에 다 저장을 했는데도 워크플로우가 자꾸 실패로 떠서, RUNNING_TESTS 변수가 True이면 DB를 sqlite를 활용하고, 아니면 mysql을 쓰도록 settings.py를 수정했다
# settings.py

# Check if running in the test environment
RUNNING_TESTS = os.environ.get("RUNNING_TESTS") == "True"

if RUNNING_TESTS:
    DATABASES = {
        "default": {
            "ENGINE": "django.db.backends.sqlite3",
            "NAME": BASE_DIR / "db.sqlite3",
        }
    }
else:
    MYSQL_DB = env("DB_NAME")
    if MYSQL_DB:
        DATABASES = {
            "default": {
                "ENGINE": "django.db.backends.mysql",
                "NAME": MYSQL_DB,
                "USER": env("DB_USER"),
                "PASSWORD": env("DB_PASSWORD"),
                "HOST": env("DB_HOST"),
                "PORT": env("DB_PORT"),
            }
        }
    else:
        DATABASES = {
            "default": {
                "ENGINE": "django.db.backends.sqlite3",
                "NAME": BASE_DIR / "db.sqlite3",
            }
        }
  • mysql DB 외부 접속 허용하는 걸로 바꿨는데도 안 돼서 그냥 이렇게 할 수밖에 없었다..
  • 찾아보니 mysql 설정 파일에서 뭘 바꿔야 한다고 나왔는데 cmd에서 그 설정 파일 편집기가 안 열렸다...
  • 여튼 이렇게 설정을 마치니 push를 할 때마다 워크플로우가 실행되고, 테스트 코드가 잘 통과되는 것을 확인할 수 있었다