티스토리 뷰

이전 포스트에서 Github Actions에 대해 살펴보았다.
https://hoplin-dev.tistory.com/2

 

[CI] Github Branch PR Merge 이전에 Test 진행하기 1편 - Github Action 살펴보기

이전 포스트에서 CI와 CD가 어떤 상황에서 사용되는지에 대해 알아보았다. 이번에는 CI(Continuous Integration)를 구현해 볼 것이다. 현재 초기 개발단계인 프로젝트는 dev 브랜치에 병합을 하면 Developmen

hoplin-dev.tistory.com

이제 실제 프로젝트에서 Github Actions를 사용하여 dev브랜치에 Pull Request를 생성하는 경우 테스트를 실행하고 통과하는 경우에만 Pull Request할 수 있도록 해보자.
 
들어가기 앞서 이 시리즈는 결론적으로 아래 사진의 Github Actions Flow를 작성할 것이며, 각각의 단계에 대해서 Discord 알림이 가도록 설정할 것이다. 

처음부터 위 사진처럼 만드는것이 아닌 필자가 Github Actions를 공부하면서 작성해보고 시행착오를 겪었던 순서대로 시리즈를 이어나갈 것이다.

Github Actions Job 파악하기

우선 어떤 Job이 있어야 하는지 정해본다. CI의 정석적인 Job은 아니지만, 지금은 테스트만 진행한다고 가정한다.
 

  1. Unit Test
  2. E2E Test

여기서는 E2E Test는 Unit Test가 실행된 이후에 실행되어야 한다는 가정을 진행한다. 이제 Actions Script를 순차적으로 작성해보자.
 

Github Actions Script 기본 설정하기

name & run-name

먼저 기본적으로 Github Actions Script의 기본적인 설정을 진행해보자. 우선 Optional이지만 Action의 Name을 보여주기 위한 name필드현재 Workflow의 이름을 표기할 run-name필드를 작성해준다. run-name필드는 githubinputs 컨텍스트를 참조할 수 있다. 이를 활용하여 작성해보자. 
 
필자는는 Event를 Trigger한 사람의 이름과 Pull Request의 Nuber를 표시하게끔 설정하였다.(github 컨텍스트의 ref_name같은 경우 Pull Request에 대해서는 pr_number를 표시해준다.)

name: CI-Unit-Test
run-name: Unit/E2E test of ${{ github.ref_name }} by ${{ github.actor }}

 

on

이제 Actions가 Trigger되는상황에 대한 세부정의를 해보자. 테스트가 진행되는것은 dev브랜치고 Pull Request가 발생했을때이다. Github Actions는 on.<event_name>.types 형태로 어떤 Event가 Trigger 될때 Workflow가 실행될지 정의할 수 있다. <event_name>에 들어갈 수 있는 이벤트들은 이 링크를 참고하자. 
 
event는 pull_request를 사용할 것이며, 대상 브랜치는 dev브랜치로 지정한다. (이 이외에도 pull request type들과 특정 파일이 변경됐을때만 진행하는 등 세부적인 설정도 가능하다.(링크))

on:
  pull_request:
    branches:
      - dev

 
Workflow 스크립트는 이제 아래와 같은 형태를 띄고 있을것이다.

name: CI-Unit-Test
run-name: Unit/E2E test of ${{ github.ref_name }} by ${{ github.actor }}
on:
  pull_request:
    branches:
      - dev

 

Github Actions Job 설정하기

Job ID 설정하기

이제 Actions에서 실행할 Job을 정의해야한다. job은 jobs필드에 정의해야한다. 기본적으로 모든 job은 <job_id>를 정의해 주어야 한다. <job_id>는 각각의 job에 대한 Unique ID이다. 즉 각각의 job들은 서로 다른 이름을 가지고 있어야 한다.(링크)

우선 Unit Test가 이루어지는 job의 이름은 unit-testing 그리고 E2E Test가 이루어지는 job의 이름은 e2e-testing으로 작성한다.

jobs:
  unit-testing:
  e2e-testing:

 

Job Runtime 설정하기

이제 각각의 Job이 실행될 Runtime 환경을 정의해 주어야한다. Runtime 환경은 jobs.<job_id>.runs-on에 정의해야한다. 필자는 Ubuntu Runtime을 활용할 것이며, 최신버전을 사용할 것이다. 필요에 따라 버전을 명시할 수 있다.(ubuntu-latest로 지정하면 최신버전의 Ubuntu를 사용한다.)

 
또한 필자는 사용하지 않았지만, 필요에 따라 Container Runtime을 활용할 수 도 있다.(링크)

jobs:
  unit-testing:
    runs-on: ubuntu-latest

 

Steps 설정하기

이제 Job을 구성하고 있는 Steps들에 대해 정의해야한다. 기존 포스트에서도 봤지만 Steps들은 순차적으로 동작한다. Step을 구성할때는 내 프로젝트가 Clone 되기 이전의 환경에서 실행을 한다고 생각해야한다. 또한 Github Actions는 Github에서 제공하는것 뿐만 아니라  Third-Party Action들을 Marketplace에서 제공한다. Marketplace에서 제공하는 Action를 사용하면 일일히 Step별로 스크립트를 작성할 필요없이 간단하게 다양한 기능을 활용할 수 있다.(적극 활용해보는것을 추천한다.) 또한 필요에 따라 Action Script를 직접 제작할 수 도 있다.
 
시작하기 앞서 테스트를 실행하기 위한 과정을 생각해본다.(독자의 상황은 필자의 상황과 다를 수 도 있습니다)
 

  1. Node.js 환경을 Runtime에 설정해 주어야한다
  2. Repository를 Clone한다.
  3. CI환경에서 사용하기 위한 .ci.env 파일을 만든다
  4. Repository의 Dependency를 설치한다
  5. npm scripts에서 활용하기 위한 dotenv cli를 설치한다
  6. 테스트 실행

 

1. Node.js 환경 설정

 
먼저 Node.js Runtime설정을 해주어야 한다. 직접 Node.js를 설치하는 command를 작성해줘도 되지만, Github에서 제공하는 setup-node를 활용하면 간단히 정의해줄 수 있다. setup-node는 Runtime에서 Node.js환경을 초기화해주는 Action을 제공해준다. 여기서는 Node.js v18 환경으로 초기화한다.
 
Marketplace에서 제공하는 Action들은 대부분 Readme에 사용방법을 제공하므로 Readme를 참고하자.
https://github.com/actions/setup-node/blob/main/README.md
 
Marketplace에서 제공하는것과 같이 사전 정의된 Action을 사용하기 위해서는 uses키워드를 활용한다. 또한 밑에서 보겠지만 일반적인 명령어(terminal에서 쓰는 명령어)를 실행하기 위해서는 run키워드를 사용한다.

    steps:
      # Setup environment for Node.js 18
      - name: Node.js version 18 Environment setting
        uses: actions/setup-node@v4
        with:
          node-version: 18

 
 

2. Repository Clone
 

Repository를 Clone하는것도 위와 동일하게 Github에서 제공하는 checkout Action을 활용하면 된다. 기본적인 Clone부터 Push등 여러가지 행동을 정의할 수 있다.
https://github.com/actions/checkout

 

GitHub - actions/checkout: Action for checking out a repo

Action for checking out a repo. Contribute to actions/checkout development by creating an account on GitHub.

github.com

    steps:
      # Setup environment for Node.js 18
      - name: Node.js version 18 Environment setting
        uses: actions/setup-node@v4
        with:
          node-version: 18
      # Checkout repository codes
      - name: Repository Checkout
        uses: actions/checkout@v4

 

3. .ci.env 생성하기

 
현재 프로젝트에서는 CI 환경에서 테스트를 위해서 .ci.env로부터 환경변수를 불러오도록 npm scripts를 작성하였다.

 
.ci.envGithub Actions의 secrets 컨텍스트를 활용하여 작성한다. secrets컨텍스트는 Workflow에서 활용할 수 있는 Secret값들을 의미하며, API Key와 같이 중요 환경변수들을 사용하기 위해 사용한다. secrets 컨텍스트에 들어가는 환경변수들은 Repository Setting에서 정의할 수 있다.

위 사진과 같이 Settings-Actions의 Repository Secrets에 값들을 넣으면 secrets 컨텍스트에서 접근이 가능하다. 접근 방법은 secrets.<secret 이름> 방식으로 접근할 수 있다. .ci.env는 기본적으로 Repository에는 존재하지 않는 파일이므로 만들어야 한다. 만드는 방법은 일반적인 리눅스에서 echo를 활용하여 파일에 값을 넣는 방식을 활용한다.
 

    steps:
      # Setup environment for Node.js 18
      - name: Node.js version 18 Environment setting
        uses: actions/setup-node@v4
        with:
          node-version: 18
      # Checkout repository codes
      - name: Repository Checkout
        uses: actions/checkout@v4
        
      - name: Initialize .ci.env
        run: |
          touch .ci.env
          echo "DATABASE_URL=${{ secrets.DATABASE_URL}}" >> .ci.env
          echo "ADMIN_EMAIL=${{secrets.ADMIN_EMAIL}}" >> .ci.env
          echo "ADMIN_PW=${{secrets.ADMIN_PW}}" >> .ci.env
          echo "JWT_SECRET=${{secrets.JWT_SECRET}}" >> .ci.env
          echo "JUDGE_SERVER_ENDPOINT=${{secrets.JUDGE_SERVER_ENDPOINT}}" >> .ci.env
          echo "AWS_REGION"=${{secrets.AWS_REGION}}>> .ci.env
          echo "AWS_ACCESS_ID=${{secrets.AWS_ACCESS_ID}}" >> .ci.env
          echo "AWS_ACCESS_SECRET=${{secrets.AWS_ACCESS_SECRET}}" >> .ci.env
          echo "AWS_SQS_QUEUE=${{secrets.AWS_SQS_QUEUE}}" >> .ci.env
          echo "AWS_S3_BUCKET=${{secrets.AWS_S3_BUCKET}}" >> .ci.env

 

4. Repository Dependency & dotenv CLI 설치하기

이제 프로젝트의 패키지 의존성을 설치해준다. 의존성을 설치하는 방식 또한 일반적인 명령어 실행 방식과 동일하다. Dotenv CLI는 Global Scope로 설치해준다.

    steps:
      # Setup environment for Node.js 18
      - name: Node.js version 18 Environment setting
        uses: actions/setup-node@v4
        with:
          node-version: 18
      # Checkout repository codes
      - name: Repository Checkout
        uses: actions/checkout@v4
        
      - name: Initialize .ci.env
        run: |
          touch .ci.env
          echo "DATABASE_URL=${{ secrets.DATABASE_URL}}" >> .ci.env
          echo "ADMIN_EMAIL=${{secrets.ADMIN_EMAIL}}" >> .ci.env
          echo "ADMIN_PW=${{secrets.ADMIN_PW}}" >> .ci.env
          echo "JWT_SECRET=${{secrets.JWT_SECRET}}" >> .ci.env
          echo "JUDGE_SERVER_ENDPOINT=${{secrets.JUDGE_SERVER_ENDPOINT}}" >> .ci.env
          echo "AWS_REGION"=${{secrets.AWS_REGION}}>> .ci.env
          echo "AWS_ACCESS_ID=${{secrets.AWS_ACCESS_ID}}" >> .ci.env
          echo "AWS_ACCESS_SECRET=${{secrets.AWS_ACCESS_SECRET}}" >> .ci.env
          echo "AWS_SQS_QUEUE=${{secrets.AWS_SQS_QUEUE}}" >> .ci.env
          echo "AWS_S3_BUCKET=${{secrets.AWS_S3_BUCKET}}" >> .ci.env
      - name: Install Node.js Dependencies
        run: npm install --force
      - name: Install dotenv-cli for dotenv cli
        run: npm install -g dotenv-cli

 

5. 테스트 실행

이제 테스트를 실행한다. 위 사진에서 볼 수 있듯이 CI환경에서 Unit Test를 실행하기 위해 ci:unit이라는 명령어를 정의해 두었다.

    steps:
      # Setup environment for Node.js 18
      - name: Node.js version 18 Environment setting
        uses: actions/setup-node@v4
        with:
          node-version: 18
      # Checkout repository codes
      - name: Repository Checkout
        uses: actions/checkout@v4
        
      - name: Initialize .ci.env
        run: |
          touch .ci.env
          echo "DATABASE_URL=${{ secrets.DATABASE_URL}}" >> .ci.env
          echo "ADMIN_EMAIL=${{secrets.ADMIN_EMAIL}}" >> .ci.env
          echo "ADMIN_PW=${{secrets.ADMIN_PW}}" >> .ci.env
          echo "JWT_SECRET=${{secrets.JWT_SECRET}}" >> .ci.env
          echo "JUDGE_SERVER_ENDPOINT=${{secrets.JUDGE_SERVER_ENDPOINT}}" >> .ci.env
          echo "AWS_REGION"=${{secrets.AWS_REGION}}>> .ci.env
          echo "AWS_ACCESS_ID=${{secrets.AWS_ACCESS_ID}}" >> .ci.env
          echo "AWS_ACCESS_SECRET=${{secrets.AWS_ACCESS_SECRET}}" >> .ci.env
          echo "AWS_SQS_QUEUE=${{secrets.AWS_SQS_QUEUE}}" >> .ci.env
          echo "AWS_S3_BUCKET=${{secrets.AWS_S3_BUCKET}}" >> .ci.env
      - name: Install Node.js Dependencies
        run: npm install --force
      - name: Install dotenv-cli for dotenv cli
        run: npm install -g dotenv-cli
      - name: Run Unit Test
        run: npm run ci:unit

 

7. E2E Test step 작성하기

E2E Test도 이와 동일하게 작성한다.

name: CI-Unit-Test
run-name: Unit/E2E test of ${{ github.ref_name }} by ${{ github.actor }}
on:
  pull_request:
    branches:
      - dev
jobs:
  unit-testing:
    runs-on: ubuntu-latest
    steps:
      # Setup environment for Node.js 18
      - name: Node.js version 18 Environment setting
        uses: actions/setup-node@v4
        with:
          node-version: 18
      # Checkout repository codes
      - name: Repository Checkout
        uses: actions/checkout@v4
        
      - name: Initialize .ci.env
        run: |
          touch .ci.env
          echo "DATABASE_URL=${{ secrets.DATABASE_URL}}" >> .ci.env
          echo "ADMIN_EMAIL=${{secrets.ADMIN_EMAIL}}" >> .ci.env
          echo "ADMIN_PW=${{secrets.ADMIN_PW}}" >> .ci.env
          echo "JWT_SECRET=${{secrets.JWT_SECRET}}" >> .ci.env
          echo "JUDGE_SERVER_ENDPOINT=${{secrets.JUDGE_SERVER_ENDPOINT}}" >> .ci.env
          echo "AWS_REGION"=${{secrets.AWS_REGION}}>> .ci.env
          echo "AWS_ACCESS_ID=${{secrets.AWS_ACCESS_ID}}" >> .ci.env
          echo "AWS_ACCESS_SECRET=${{secrets.AWS_ACCESS_SECRET}}" >> .ci.env
          echo "AWS_SQS_QUEUE=${{secrets.AWS_SQS_QUEUE}}" >> .ci.env
          echo "AWS_S3_BUCKET=${{secrets.AWS_S3_BUCKET}}" >> .ci.env
      - name: Install Node.js Dependencies
        run: npm install --force
      - name: Install dotenv-cli for dotenv cli
        run: npm install -g dotenv-cli
      - name: Run Unit Test
        run: npm run ci:unit
  e2e-testing:
    runs-on: ubuntu-latest
    steps:
      # Setup environment for Node.js 18
      - name: Node.js version 18 Environment setting
        uses: actions/setup-node@v4
        with:
          node-version: 18
      # Checkout repository codes
      - name: Repository Checkout
        uses: actions/checkout@v4
        
      - name: Initialize .ci.env
        run: |
          touch .ci.env
          echo "DATABASE_URL=${{ secrets.DATABASE_URL}}" >> .ci.env
          echo "ADMIN_EMAIL=${{secrets.ADMIN_EMAIL}}" >> .ci.env
          echo "ADMIN_PW=${{secrets.ADMIN_PW}}" >> .ci.env
          echo "JWT_SECRET=${{secrets.JWT_SECRET}}" >> .ci.env
          echo "JUDGE_SERVER_ENDPOINT=${{secrets.JUDGE_SERVER_ENDPOINT}}" >> .ci.env
          echo "AWS_REGION"=${{secrets.AWS_REGION}}>> .ci.env
          echo "AWS_ACCESS_ID=${{secrets.AWS_ACCESS_ID}}" >> .ci.env
          echo "AWS_ACCESS_SECRET=${{secrets.AWS_ACCESS_SECRET}}" >> .ci.env
          echo "AWS_SQS_QUEUE=${{secrets.AWS_SQS_QUEUE}}" >> .ci.env
          echo "AWS_S3_BUCKET=${{secrets.AWS_S3_BUCKET}}" >> .ci.env
      - name: Install Node.js Dependencies
        run: npm install --force
      - name: Install dotenv-cli for dotenv cli
        run: npm install -g dotenv-cli
      - name: Run Unit Test
        run: npm run ci:e2e

 

8. 실행순서 정의해주기

하지만 여기서 끝나면 안된다. 위에서 우리는 Unit Test가 실행된 이후 E2E Test가 실행된다는 가정을 하였다. 기본적으로 Github Actions의 Job들은 모두 동시에 실행된다. 만약 특정 Job이 다른 Job에 의존적이라면 jobs.<job_id>.needs 를 활용하여 의존성을 부여할 수 있다.

이를 활용하여 e2e-testing Job에 needs를 정의해준다.

  e2e-testing:
    needs: unit-testing
    runs-on: ubuntu-latest

완성된 Github Action Script와 실행 결과 화면이다.

name: CI-Unit-Test
run-name: Unit/E2E test of ${{ github.ref_name }} by ${{ github.actor }}
on:
  pull_request:
    branches:
      - dev
jobs:
  unit-testing:
    runs-on: ubuntu-latest
    steps:
      # Setup environment for Node.js 18
      - name: Node.js version 18 Environment setting
        uses: actions/setup-node@v4
        with:
          node-version: 18
      # Checkout repository codes
      - name: Repository Checkout
        uses: actions/checkout@v4
        
      - name: Initialize .ci.env
        run: |
          touch .ci.env
          echo "DATABASE_URL=${{ secrets.DATABASE_URL}}" >> .ci.env
          echo "ADMIN_EMAIL=${{secrets.ADMIN_EMAIL}}" >> .ci.env
          echo "ADMIN_PW=${{secrets.ADMIN_PW}}" >> .ci.env
          echo "JWT_SECRET=${{secrets.JWT_SECRET}}" >> .ci.env
          echo "JUDGE_SERVER_ENDPOINT=${{secrets.JUDGE_SERVER_ENDPOINT}}" >> .ci.env
          echo "AWS_REGION"=${{secrets.AWS_REGION}}>> .ci.env
          echo "AWS_ACCESS_ID=${{secrets.AWS_ACCESS_ID}}" >> .ci.env
          echo "AWS_ACCESS_SECRET=${{secrets.AWS_ACCESS_SECRET}}" >> .ci.env
          echo "AWS_SQS_QUEUE=${{secrets.AWS_SQS_QUEUE}}" >> .ci.env
          echo "AWS_S3_BUCKET=${{secrets.AWS_S3_BUCKET}}" >> .ci.env
      - name: Install Node.js Dependencies
        run: npm install --force
      - name: Install dotenv-cli for dotenv cli
        run: npm install -g dotenv-cli
      - name: Run Unit Test
        run: npm run ci:unit
  e2e-testing:
    needs: unit-testing
    runs-on: ubuntu-latest
    steps:
      # Setup environment for Node.js 18
      - name: Node.js version 18 Environment setting
        uses: actions/setup-node@v4
        with:
          node-version: 18
      # Checkout repository codes
      - name: Repository Checkout
        uses: actions/checkout@v4
        
      - name: Initialize .ci.env
        run: |
          touch .ci.env
          echo "DATABASE_URL=${{ secrets.DATABASE_URL}}" >> .ci.env
          echo "ADMIN_EMAIL=${{secrets.ADMIN_EMAIL}}" >> .ci.env
          echo "ADMIN_PW=${{secrets.ADMIN_PW}}" >> .ci.env
          echo "JWT_SECRET=${{secrets.JWT_SECRET}}" >> .ci.env
          echo "JUDGE_SERVER_ENDPOINT=${{secrets.JUDGE_SERVER_ENDPOINT}}" >> .ci.env
          echo "AWS_REGION"=${{secrets.AWS_REGION}}>> .ci.env
          echo "AWS_ACCESS_ID=${{secrets.AWS_ACCESS_ID}}" >> .ci.env
          echo "AWS_ACCESS_SECRET=${{secrets.AWS_ACCESS_SECRET}}" >> .ci.env
          echo "AWS_SQS_QUEUE=${{secrets.AWS_SQS_QUEUE}}" >> .ci.env
          echo "AWS_S3_BUCKET=${{secrets.AWS_S3_BUCKET}}" >> .ci.env
      - name: Install Node.js Dependencies
        run: npm install --force
      - name: Install dotenv-cli for dotenv cli
        run: npm install -g dotenv-cli
      - name: Run Unit Test
        run: npm run ci:e2e

 

 
기본적으로 e2e-testing Job은 unit-testing Job에 의존적이기 때문에, unit-testing이 실패하면 동작을 멈춘다.

하지만 만약 의존하고 있는 Job의 성공 여부 상관없이 항상 실행하고 싶다면 아래 방법을 활용하면 된다.
 

이 포스트에서는 단순히 테스트를 실행하는 Action Script를 작성했다. 이후 포스트에서는 CI에 부합하는 Action스크립트를 재설계하고 구현해볼 것이다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함