소설위키(FDBS)를 배포하고 리팩토링하는 과정에서 예기치 않은 에러가 발생하여, 실제 서비스 이용이 어려워지는 상황이 생겼다.
Vercel 자체적으로 빌드타임에 에러를 잡아내긴 하지만, 직접적인 에러가 발생하지 않아도 코드가 문제를 일으키는 경우가 있다.
이런 상황을 미연에 방지하기 위해 CI/CD 개념을 도입하여 서비스의 빌드 및 테스트, 배포 과정을 자동화해보기로 결정하였다.
CI = 지속적인 통합(Continuous Integration); 한마디로 “빌드와 테스트 자동화”
CD or CDep= 지속적인 전달(Continuous Delivery) 또는 지속적인 배포(Continuous Deployment); 한마디로 “배포 자동화” .
Cdep는 main 브랜치로 병합 후 프로덕션 레벨까지 자동배포하는 프로세스까지 자동화함을 의미한다.
좀 더 쉽게 표현하면, CI는 코드변경을 공용 저장소에 통합시키는 과정을 자동화 하는것이고, CD는 통합 이후 프로덕션 단계까지의 과정을 자동화함을 의미한다.
이러한 CI/CD 개념의 도입을 통해 서비스나 어플리케이션의 안정적인 성능을 보장할 수 있고, 버그를 최소화할 수 있다.
기존에는 별도의 CI과정 없이 Vercel에서 제공하는 자동화된 빌드, 배포 및 호스팅을 서비스를 이용하고 있었다.
Vercel 내에서 프로젝트를 생성하여, Github 레포지토리를 import해주기만 하면 이후 깃허브 레포지토리 내 main 브랜치에 코드 변경이 일어날때마다 자동으로 프로덕션 단계까지 자동으로 이어지는 방식이다.
하지만 앞서 언급하였듯이, 코드에 에러나 문제가 있더라도 빌드과정중에 런타임 에러만 발생하지 않는다면, 문제없이 바로 배포까지 이어지기 때문에, 안정적인 서비스를 목표로 한다면 부족한 부분이 적지 않다.
dev ——— main ——— production
FDBS에는 dev, main 두개의 주요 브랜치가 존재한다.
목표로 하는 개발 흐름은 다음과 같다.
1) 로컬의 dev 브랜치에서 github의 dev 레포로 push.
2) Featrue 단위로 dev 브랜치에서 main 브랜치로 PR을 생성.
3) PR 생성시, 빌드 및 테스트 과정(CI)이 진행.
따라서 기존의 머지~배포 파이프라인에서 CI 과정을 추가하고 이 과정에서 테스팅 작업을 추가하는 것이 주요 과제가 될 것이다.
Github에서 제공하는 github actions를 사용하면, 손쉽게 CI/CD 파이프라인을 만들 수 있다.
작업 흐름은 다음과 같다.
1. dev에서 main으로 PR 생성시 Vercel에서 서비스 빌드
2. 빌드된 URL을 통해 E2E 테스트 실행
3. 테스트가 통과될때만 Merge.
name: Playwright Tests
# 실행트리거: Main, branch로 PR 및 push시 test 실행.
on:
push:
branches: [main, master]
pull_request:
branches: [main, master]
jobs:
test:
timeout-minutes: 60
# 실행환경
runs-on: ubuntu-latest
steps:
# Vercel에 빌드된 URL 가져오기 및 대기
- name: Waiting for 200 from the Vercel Preview
uses: patrickedqvist/wait-for-vercel-preview@v1.2.0
id: waitForDeploy
with:
token: ${{ secrets.GITHUB_TOKEN }}
max_timeout: 300
# 프로젝트 소스코드 내려받기
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
cache: "yarn"
- name: Enable Corepack
run: corepack enable
# 종속성 설치
- name: Install dependencies
run: yarn install --immutable
- name: Install Playwright Browsers
run: yarn playwright install --with-deps
# 테스트 실행
- name: Run Playwright tests
run: yarn test:e2e
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
...
# 빌드된 URL로 테스트 실행
BASE_URL: ${{ steps.waitForDeploy.outputs.url }}
- uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
yarn create playwright
다음 명령어를 사용하면, 필요로 하는 playwright 관련 의존성 모듈들이 설치된다.
// .eslintrc.json
...
"extends": [
"eslint:recommended",
"next",
"plugin:tailwindcss/recommended",
"plugin:testing-library/react",
"plugin:jest-dom/recommended",
"plugin:playwright/recommended"
],
...
이후 위처럼 타입스크립트, eslint 설정을 위한 플러그인 등의 설정을 해주고
// playwright.config.ts
...
// 로컬 - CI 환경 구분, CI환경에서의 Vercel의 Preview URL을 사용을 위한 설정
webServer: process.env.CI
? undefined
: {
command: `yarn dev`,
url: BASE_URL,
timeout: 120 * 1000,
reuseExistingServer: true,
},
});
생성된 playwright.config.ts를 통해 세부설정을 조정하면 기본세팅을 완료할 수 있다.
tests 폴더를 생성하여 다음과 같은 코드를 작성하면 pacakgage.json에 명시된 스크립트 코드 실행을 통해 로컬 환경에서 테스트를 진행할 수 있다.
//example.spec.ts
import { test, expect } from "@playwright/test";
const BASE_URL = process.env.BASE_URL || "http://localhost:3000";
test("should navigate to the about page", async ({ page }) => {
await page.goto(BASE_URL);
await page.click("text=소설위키");
await expect(page).toHaveURL(BASE_URL);
});
성공적으로 PR에 Plawywright 테스트를 반영할 수 있게 되었다.
프론트엔드에서 테스트를 진행할 수 있는 방법은 다양하다. 내 서비스에는 어떤 테스팅이 필요할까?
Unit testing
소프트웨어의 가장 작은 단위(주로 함수나 컴포넌트)를 독립적으로 검사하는 테스트
Integration testing
여러 단위들이 함께 작동할 때의 동작을 검사.
단위 테스트보다 더 큰 범위의 기능을 테스트하며, 서로 다른 모듈 또는 컴포넌트 간의 상호작용을 검증한다.
E2E testing
사용자의 관점에서 애플리케이션의 흐름을 전체적으로 테스트한다. 실제 사용자가 시스템을 사용하는 것처럼 시나리오를 실행하여 기능이 전반적으로 정상 작동하는지 확인한다.
Snapshot testing
UI 컴포넌트의 렌더링 결과를 '스냅샷'으로 저장하고, 이후 테스트에서 이 스냅샷과 비교하여 UI 변경사항을 감지한다.
주로 UI가 예상대로 렌더링되는지 확인하는 데 사용됩니다.
이외에도 성능,접근성,시각적 회귀 등 여러 측면에서 테스팅이 가능하다.
React에서 사용할 수 있는 테스팅 라이브러리에는 Jest, Vitest, playwright, cypress 등등이 있다.
현재 FDBS는 Next.js 13버젼에 기반하고 있고, Server component 기능들을 사용하고 있기 때문에, 이를 지원하지 않으며, 유닛 테스팅에 중점을 둔 jest나 vitest 보다는 E2E 라이브러리를 도입하기로 결정하였다.
Good to know: Since async Server Components are new to the React ecosystem, Jest currently does not support them. While you can still run unit tests for synchronous Server and Client Components, we recommend using an E2E tests for async components.
주요 E2E 라이브러리에는 cypress와 playwright가 있다.
두 라이브러리를 좀 더 자세히 비교해 보자.
Cypress는 모든 테스트가 직렬로 실행된다. 따라서 테스트 파일의 갯수가 증가하는만큼 시간이 소요된다. 병렬 테스팅이 가능하긴하지만 유료 플랜에서만 가능하다.
반면 playwright는 무료 병렬 테스팅이 가능하다.
Cypress는 자체적인 API와 문법을 숙지해야 한다.
하지만 Playwright는 특정 명령어 몇개만 숙지하면 async await에 기반하여 JS 코드를 작성하는 것처럼 손쉽게 코드를 작성할 수 있기 때문에 낮은 러닝커브가 상대적으로 낮다.
playwright와 달리 Cypress는 hover,drag를 공식적으로 지원하지 않는다.
Cypress는 크로미움 브라우저 및 파이어폭스만을 지원하고 있으며, playwright는 모든 webkit기반 브라우저 및 모바일 브라우저들을 모두 지원하고 있다.
여러가지 측면에서 비교해 보았으나, 여러모로 playwright가 성능상이나 사용성 측면에서나 많은 이점이 있는 것으로 판단된다.
따라서 E2E에 중점을 둔 테스팅 라이브러리 중 더 많은 기능을 지원하고 있고, 큰 프로젝트에 적합한 playwright를 사용하기로 결정하였다.
https://katalon.com/resources-center/blog/ci-cd-introduction
https://kontent.ai/blog/next-js-playwright-tests-github-action/
https://playwright.dev/docs/ci