[Node] git action을 이용한 CI

김나나·2024년 9월 9일

Node.js

목록 보기
43/50

인턴으로 있는 기간동안 많은 것들을 경험할 수 있도록 도와주셔서 늘 감사한 마음으로..
이번에는 git action을 사용해서 CI/CD 를 구축해볼 수 있는 시간을 갖게 되었다.

해당 velog 글을 참고하였다!


  1. 깃허브에 repo 하나 생성 후,

  2. 빈 폴더 하나를 만들고, 터미널창에 npm init -y을 입력하여 "package.json"생성

  3. package.json에 아래처럼 작성(일단 script 부분은 비워두기)

{
  "name": "ci-cd-test",
  "version": "1.0.0",
  "description": "ci-cd-test",
  "main": "index.js",
  "scripts": {},
  "repository": {
    "type": "git",
    "url": "git+https://github.com/kimLrLr/node-ci-cd-test.git"
  },
  "author": "kimLrLr",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/kimLrLr/node-ci-cd-test/issues"
  },
  "homepage": "https://github.com/kimLrLr/node-ci-cd-test#readme"
}


✨폴더구조

실제 기능을 담은 js파일, 이를 테스트할 파일
이렇게 두 가지의 파일을 분리시켜주는데,
test할 파일을 정리하기 위한 폴더 구조에는 크게 2가지가 있다.

1. 실제 기능을 담은 js파일과 테스트할 파일을 같은 위치에 둔다.

보통 React에서 해당 방식을 많이 사용한다고 하고,
해당 경우에는 아래와 같은 폴더 구조를 갖는다.

├── src
│   ├── index.js
│   └── index.test.js

2. src와 test폴더를 따로 둔다.

해당 경우에는 아래와 같은 폴더 구조를 갖는다.

├── src
│   └── index.js
├── test
│   ├── integration
│   │	└── index.test.js
│   └── unit

src 폴더에는 실제 기능을 담은 js파일을 두고,
test 폴더에는 integration 폴더와 unit 폴더를 둔다.
여기서 integration 폴더에는 통합 테스트를 진행하는 테스트 코드 파일을,
unit 폴더에는 단위 테스트를 진행하는 테스트 코드 파일을 넣을 수 있게 구조를 설정할 수 있다.


  1. 위에서 설명된 2번 폴더구조를 이용해 아래처럼 구조를 만들어준다.
├── src
│	├── functions
│   │	├── add.js
│   │	└── sub.js
│   └── index.js
├── test
│   ├── integration
│   │	└── index.test.js
│   ├── unit
│   │	├── add.test.js
│   │	└── sub.test.js


✨Code 작성

  1. 만들어둔 add.js와 sub.js, index.js에 테스트를 위한 간단한 코드 작성

아래는 add.js로, a와 b의 값을 더해 return해주는 코드

export const add = (a, b) => {
  return a + b;
};

아래는 sub.js로, a와 b의 값을 빼서 return해주는 코드

export const sub = (a, b) => {
  return a - b;
};

아래는 index.js로, a와 b를 더한 값 + a와 b를 뺀 값을 return해주는 코드

export const main = (a, b) => {
  return add(a, b) + sub(a, b);
};

✨ESLint 적용

코드 스타일을 체크하기 위한 라이브러리인 ESLint를 프로젝트에 적용해보자.

  1. 터미널을 열어 아래 코드 입력하여 ESLint 설치
npm install eslint --save-dev
  1. .gitignore를 하나 만들어 "node_modules"폴더가 git에 올라가지 않도록 설정
node_modules/

  1. 이후 터미널을 열고 아래 명령어 입력
npm add eslint@8.0.1

=> eslint 9버전부터 eslintrc가 아니라 완전 바뀌었길래 8버전 사용을 위해 입력

npx eslint --init

설정은 해당 velog를 참고하며 진행하였다.

.eslintrc.json파일이 생성되면 아래처럼 작성되어있다.

{
    "env": {
        "browser": true,
        "es2021": true,
        "node": true
    },
    "extends": "eslint:recommended",
    "parserOptions": {
        "ecmaVersion": 15,
        "sourceType": "module"
    },
    "rules": {
    }
}

  1. 이후 아래 코드가 .eslintrc.json에서 몇 가지 셋팅을 해준 코드!
{
  "env": {
    "browser": true,
    "es2021": true
  },
  "extends": "standard",
  "overrides": [],
  "parserOptions": {
    "ecmaVersion": "latest",
    "sourceType": "module"
  },
  "rules": {
    "semi": [2, "always"],
    "no-unused-vars": "warn"
  }
}

ESLint 상세 설정 가이드 해당 글 참고

변경사항은 standard를 extend해주고,
no-unused-vars로 사용되지 않는 변수들이 존재하는 경우에 error처리가 아닌 warn으로 표시되도록 설정..!


✨Prettier 적용

앞에서 ESLint로 코드 스타일을 체크해줬다면,
Prettier로 코드 포맷팅 설정을 해주자..!
프리티어는 기본적으로 프로젝트의 root에 있는 .prettierrc파일에 적힌 룰에 의해 동작한다.

  1. 우선 .prettierrc파일을 생성해준 뒤, 아래처럼 설정해주자
{
    "bracketSpacing": false,
    "jsxBracketSameLine": true,
    "semi": true,
    "singleQuote": true,
    "trailingComma": "all",
    "arrowParens": "avoid",
    "endOfLine": "auto",
    "tabWidth": 2
  }
  1. 터미널에 아래 코드를 입력하여 prettier 설치
npm install prettier --save-dev
  1. 이까지 했으면 ESLint와 Prettier 연동을 위해
    터미널을 열어 아래 코드 입력하여 "eslint-config-prettier"설치
npm install eslint-config-prettier --save-dev
  1. 설치가 끝났으면 아까 만들었던 eslintrc.json 파일을 아래와 같이 수정!
{
  "env": {
    "browser": true,
    "es2021": true
  },
  "extends": ["standard", "prettier"],
  "overrides": [],
  "parserOptions": {
    "ecmaVersion": "latest",
    "sourceType": "module"
  },
  "rules": {
    "semi": [2, "always"],
    "no-unused-vars": "warn"
  }
}

!주의! prettier를 extends의 마지막에 넣어줘야 ESLint 코드 스타일 설정을 덮어쓸 수 있음~!


✨yml파일 설정

이까지 큰 오류없이 잘 되었으면 자동화 프로세스를 설정할 수 있다!

  1. 루트에서 .github/workflows디렉토리 생성 후,
    workflows안에 ci.yml파일을 생성하여 넣어줌

아래는 ci.yml파일의 기본 포맷

name: Node.js CI

# 구독할 이벤트
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

# jobs 단위로 개별 서버(정확히는 Docker 컨테이너 단위라고 한다.)에서 작업이 수행된다.
# 각 작업은 병렬로 실행 된다고 하는데, needs: build와 같이 표시해서 기다릴 수도 있다.
jobs:
  build:
    # Ubuntu, Windows, MacOS를 지원한다.
    runs-on: ubuntu-latest

    # node-version 과 같이 배열로 돼있으면, 해당 원소를 순회하면서 작업이 반복해서 실행된다.
    # 응용해서 runs-on에 여러 OS에서 돌릴 수도 있다.
    strategy:
      matrix:
        node-version: [14.x] # 템플릿 기본값: [10.x, 12.x, 14.x]

    # uses 개념은 다른 사람이 작성한 내용을 실행하는 개념이다.
    # actions/checkout: GitHub의 마지막 커밋으로 Checkout 한다.
    # actions/setup-node: Node.js를 설치한다.
    # run 개념은 명령어를 실행한다. 셸 스크립트와 동일하다.
    steps:
      - uses: actions/checkout@v2
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v1
        with:
          node-version: ${{ matrix.node-version }}
      # npm ci는 npm install과 같은 기능을 수행한다.
      - run: npm ci
      # --if-present 옵션은 npm 스크립트가 존재할 때만 실행시키라는 의미이다.
      # 만약 build 스크립트가 없는 경우, 오류 없이 지나간다.
      - run: npm run build --if-present
      - run: npm test
  1. ESLint를 package.json에 script로 설정하기 위해
    package.json을 아래처럼 수정해준다.
{
  "name": "ci-cd-test",
  "version": "1.0.0",
  "description": "ci-cd-test",
  "main": "index.js",
  "scripts": {
    "lint": "./node_modules/.bin/eslint ."
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/kimLrLr/node-ci-cd-test.git"
  },
  "author": "kimLrLr",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/kimLrLr/node-ci-cd-test/issues"
  },
  "homepage": "https://github.com/kimLrLr/node-ci-cd-test#readme",
  "devDependencies": {
    "@eslint/js": "^9.10.0",
    "eslint": "^9.10.0",
    "eslint-config-prettier": "^9.1.0",
    "globals": "^15.9.0",
    "prettier": "^3.3.3"
  }
}

  1. 이제 ci.yml에 아래 명령어를 steps에 추가
- run: npm run lint

아래는 현재 yml파일의 전체 코드

name: Node.js CI

# 구독할 이벤트
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

# jobs 단위로 개별 서버(정확히는 Docker 컨테이너 단위라고 한다.)에서 작업이 수행된다.
# 각 작업은 병렬로 실행 된다고 하는데, needs: build와 같이 표시해서 기다릴 수도 있다.
jobs:
  build:
    # Ubuntu, Windows, MacOS를 지원한다.
    runs-on: ubuntu-latest

    # node-version 과 같이 배열로 돼있으면, 해당 원소를 순회하면서 작업이 반복해서 실행된다.
    # 응용해서 runs-on에 여러 OS에서 돌릴 수도 있다.
    strategy:
      matrix:
        node-version: [14.x] # 템플릿 기본값: [10.x, 12.x, 14.x]

    # uses 개념은 다른 사람이 작성한 내용을 실행하는 개념이다.
    # actions/checkout: GitHub의 마지막 커밋으로 Checkout 한다.
    # actions/setup-node: Node.js를 설치한다.
    # run 개념은 명령어를 실행한다. 셸 스크립트와 동일하다.
    steps:
      - uses: actions/checkout@v2
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v1
        with:
          node-version: ${{ matrix.node-version }}
      # npm ci는 npm install과 같은 기능을 수행한다.
      - run: npm ci
      # --if-present 옵션은 npm 스크립트가 존재할 때만 실행시키라는 의미이다.
      # 만약 build 스크립트가 없는 경우, 오류 없이 지나간다.
      - run: npm run build --if-present
      - run: npm run lint
      - run: npm test

추가된 곳은 아래 사진 참고하면 쉽게 찾을 수 있다!

  1. 이제 test 설정을 위해 jest 라이브러리부터 설치!
npm install jest --save-dev
  1. 이후 package.json의 script에 아래 코드 추가!
"test": "jest"

  1. 그리고 ESLint와 호환을 위해 아래 코드로 "eslint-plugin-jest"설치
npm i --save-dev eslint-plugin-jest
  1. 여기까지 했으면 루트에 eslintrc.yml파일을 생성하여 셋팅 코드 작성
env:
  jest: true # Jest 글로벌
plugins:
  - jest # Jest 테스트를 위해 플러그인이 필요하다.
rules:
  # Jest Eslint 옵션은 0,1,2 (off, warn, error) 만 옵션으로 사용 가능하다.
  jest/no-disabled-tests:
    - warn
  jest/no-focused-tests:
    - error
  jest/no-identical-title:
    - error
  jest/prefer-to-have-length:
    - warn
  jest/valid-expect:
    - error


✨Test Code

드디어 test code를 작성해볼 수 있다!
아래처럼 test code를 작성해보자.

아래는 add.test.js의 코드

import {add} from '../../src/func/add';

test('add 1 + 2 to equal 3', () => {
  expect(add(1, 2)).toBe(3);
});

아래는 sub.test.js의 코드

import {sub} from '../../src/func/sub';

test('sub 2 - 1 to equal 1', () => {
  expect(sub(2, 1)).toBe(1);
});

아래는 index.test.js의 코드

import {main} from '../../src/index';

test('main 1 , 2 to equal 2', () => {
  expect(main(1, 2)).toBe(2);
});

✨build 전 merge가 불가하도록 github에 설정해주기

GitHub의 Branch Protection Rule이라는 기능을 이용해 build 전에 merge를 할 수 없도록 설정해주자.

레포지토리 > Setting탭 > Branches탭 > Branch protection rules탭 > Add classic branch protection rule 버튼 클릭 아래와 같이 설정

설정 끝났으면 아래에 "Create" 클릭하여 만들어주자!


✨여기까지 하고 수정사항(에러 해결하기)

1. index.js에서 add와 sub를 import해준다.

2. test 파일들에 test함수와 expect 함수 정의

위 함수의 경우 jest에서 사용되는 명령어이기 때문에 몇 가지 설정을 해줘야 한다.

  1. eslintrc.json에서 env 설정
  2. babel 설정

아래에 하나씩 작성~!


🎶eslintrc.json에서 env 설정

eslintrc.json에서 env 설정에 jest 설정을 추가해줘야
jest 명령어를 ESLint와 충돌없이 사용할 수 있다고 한다.

"env"에 아래 코드 추가

"jest": true


🎶babel 설정

jest가 ES6를 지원하지 않기 때문에 ES6의 문법인 import문을 사용하면 jest에서 파싱이 불가하다.
따라서 babel을 사용해 jest가 내부적으로 babel을 통해 translation을 하여 test를 실행할 수 있도록 설정해줄 것..!

  1. 터미널에 아래 명령어를 입력하여 @babel/core@babel/preset-env를 설치
npm install @babel/core @babel/preset-env --save-dev
  1. 설치가 끝나면 루트 폴더에 babel.config.js파일을 추가하고 아래처럼 코드 작성
module.exports = {
  presets: [['@babel/preset-env', {targets: {node: 'current'}}]],
};


+) 이후 정말 오류가 와.. 정말 너무 많이 떠서..
고친 것들만 몇몇 적어보겠다..!

node버전을 20.x를 사용중이라
ci.yml에 버전 수정

일단 eslint 버전때문에 오류가 떴고,
eslint 8로 버전을 다운그레이드 하고나니

npm uninstall eslint
npm install eslint@8 --save-dev

위처럼 eslint를 8버전으로 다시 다운받으니
eslint/js랑 버전이 맞지 않아 오류가 떴다.
지금은 딱히 사용하고 있는 곳이 없어서 삭제해두고

npm uninstall @eslint/js

eslint-config-standard를 사용하기 위해

npm install --save-dev eslint-config-standard eslint-plugin-import eslint-plugin-node eslint-plugin-promise

위 명령어로 "eslint-plugin-import", "eslint-plugin-node", "eslint-plugin-promise"를 함께 설치해줘야함..!

+) eslint-plugin-promise 버전을 맞추기 위해

npm install --save-dev eslint-plugin-promise@6
profile
10분의 정리로 10시간을 아낄 수 있다는 마음으로 글을 작성하고 있습니다💕

0개의 댓글