깃허브 액션에서 E2E

깃허브 액션에서 E2E 테스트 실행하기

  • DB 또는 외부 서버와 연동된 앱을 개발할 때 도커 컴포즈를 많이 사용한다.

  • E2E 테스트에도 DB나 외부 서버와 연동이 필요할 때는 도커 컴포즈를 사용해서 시스템을 재현한다.

  • 깃허브 액션에서 E2E 테스트를 실행하기 위해 도커파일 도커 컴포즈 파일을 사용

  • 컨테이너 중에 E2E 테스트를 실행하고 종료하는 컨테이너가 있음

    • 만약 해당 컨테이너가 실행한 프로세스가 시그널이 0인 상태로 종료되면 작업은 성공한 것으로 판정

    • 반대로 1인 상태로 exit 하면 실패한것으로 판정

  • 도커 컴포즈로 연동이 필요한 컨테이너들을 실행하고 E2E 테스트를 실행하는 컨테이너를 마지막으로 실행하여 가상환경에서 E2E 테스트를 실시

도커 컴포즈(Docker Compose)란?

  • 여러 개의 도커 컨테이너를 정의하고 동시에 실행할 수 있게 해주는 도커 도구

워크플로 파일

  • Install dependencies: node_module 설치

  • Docker Compose Build: E2E 테스트용 빌드

  • Docker Compose Up As E2E Testing: E2E 테스트 실행

name: Test E2E

on: push

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: 18
      - name: Install dependencies
        run: npm ci
      - name: Docker Compose Build
        run: npm run docker:e2e:build
      - run: npm run docker:e2e:ci

npm scripts

  • -f docker-compose.e2e.yaml 옵션으로 파일명을 지정하여 빌드하고 실행

  • npm run docker:e2e:ci 가 실행에 사용되는 커맨드

    • --exit-code-from e2e : e2e라는 이름의 컨테이너 종료 시그널을 가지고 도커 컴포즈를 종료한다는 의미

    • 해당 시그널값에 따라 작업의 성패가 결정됨

// pakcage.json
{
  "scripts": {
    "docker:e2e:build": "docker compose -f docker-compose.e2e.yaml build",
    "docker:e2e:ci": "docker compose -f docker-compose.e2e.yaml up --exit-code-from e2e"
  }
}

도커파일 작성법

Docker Compose Build

{
  "scripts": {
    "docker:e2e:build": "docker compose -f docker-compose.e2e.yaml build",
  }
}
  • docker-compose 파일을 빌드

  • 빌드할 docker-compose.e2e.yaml 파일의 설정 중에는 E2E 테스트용 이미지 빌드를 위해 Dockerfile.e2e 파일이 지정되어있음

// docker-compose.e2e.yaml
services:
  db: ...
  redis: ...
  minio: ...
  createbuckets: ...
  e2e:
    build:
      context: .
      dockerfile: Dockerfile.e2e
      args:
        DATABASE_URL: postgresql://root:password@db:5432/app-db?schema=public
  • Dockerfile.e2e는 세 가지 스테이지로 구성됨

    • deps: node_modules 설치

    • builder: Next.js 애플리케이션 빌드

    • runner: 애플리케이션 및 E2E 테스트 실행

deps 스테이지

  • 의존 모듈을 설치하는 스테이지

  • package.jsonpackage-lock.json 을 복사해 node_modules 를 설치

# deps
FROM node:18-alpine As deps
RUN apk add --no-cache libc6-compat

WORKDIR /app

COPY package.json package-lock.json ./

RUN npm ci

builder 스테이지

  • 애플리케이션을 빌드하는 스테이지

  • deps 스테이지에서 설치한 node_modeuls 를 스테이지에 복사

  • 그다음 npx prisma generate 를 실행한 뒤 Next.js 앱을 빌드하기 위해 npm run build 실행

    • npx prisma generate 를 실행하는 것은 Next.js 앱에서 사용하는 객체 관계 매핑인 프리즈마에서 스키마 파일을 읽어 먼저 프리즈마 클라이언트를 생성해야 하기 땜

    • 환경 변수인 DATABASE_URL 은 프리즈마용으로 사용됨

# builder
FROM node:18 AS builder

WORKDIR /app

COPY --from=deps /app/node_modules ./node_modules
COPY ..

ARG DATABASE_URL
ENV DATABASE_URL=$DATABASE_URL
ENV NEXT_TELEMETRY_DISABLED 1

RUN npx prisma generate
RUN npm run build
  • NEXT_TELEMETRY_DISABLED 는 Next.js 빌드 시 전송되는 텔레메트리(원격 측정법)을 없애는 옵션

텔레메트리란?

  • 사용자의 Next.js 기능 사용량, 문제점 및 사용자 지정 사항을 정확하게 측정하기 위해 Next.js에서 수집하는 데이터

runner 스테이지

  • 도커 허브에 배포된 마이크로소프트의 공식 플레이라이트 이미지를 사용

  • builder 스테이지에 다음 내용을 복사

    • 빌드한 Next.js 앱

    • 프리즈마 관련 구현(E2E 테스트 실시 직전 마이그레이션을 위한)

    • E2E 테스트 파일과 플레이라이트 설정

# runner
FROM mcr.microsoft.com/playwright:v1.27.1-focal AS runner

WORKDIR /app

COPY --from=builder /app/package.json            package.json
COPY --from=builder /app/public                  public
COPY --from=builder /app/.next                   .next
COPY --from=builder /app/prisma                  ./primsma
COPY --from=builder /app/e2e                     e2e
COPY --from=builder /app/playwright.config.ts    playwright.config.ts
COPY --from=builder /app/node_modules            node_modules

EXPOSE 3000

ENV NEXT_TELEMETRY_DISABLED 1
ENV NODE_ENV production
ENV CI true
ENV PORT 3000

CMD ["npm", "run", "docker:e2e:start"]

npm scripts

  • 컨테이너를 실행하면 docker:e2e:start 라는 npm 스크립트가 실행됨

  • 이 커맨드로 다음 세 가지 작업이 순서대로 실행됨

    • npm run prisma:reset 으로 DB에 초기 데이터 주입

    • npm start 로 Next 앱 실행

    • npm run test:e2e 로 E2E 테스트 실행

// package.json
{
  "scripts": {
    "start": "next start",
    "test:e2e": "npx playwright test",
    "prisma:reset": "prisma migrate reset --force",
    "docker:e2e:start": "npm run prisma:reset && start-server-and-test 'npm start' 3000 'npm run test:e2e'",
  }
}
  • start-server-and-test 라는 npm 패키지는 애플리케이션 서버 실행과 테스트를 동시에 수행 가능

    • 애플리케이션 서버가 실행되기까지 기다렸다가 테스트를 실시한 후 테스트가 완료되면 애플리케이션 서버를 종료

도커 컴포즈 파일 작성법

{
  "scripts": {
    "docker:e2e:ci": "docker compose -f docker-compose.e2e.yaml up --exit-code-from e2e"
  }
}
  • 깃허브 액션에서 실행할 Docker Compose Up As E2E Testing

  • 도커 컴포즈로 E2E 테스트를 실시

컨테이너 간 의존 관계

  • E2E 테스트에 필요한 컨테이너들 간 의존관계를 depends_on 에 지정

  • depends_on 이 설정된 컨테이너는 의존하는 컨테이너들의 실행이 완료된 뒤 실행됨

    • ex: e2e 컨테이너는 createbuckets 가 실행될 때까지 기다렸다가 실행됨

// docker-compose.e2e.yaml
services:
  db: ...
  redis: ...
  minio: ...
  createbuckets:
    depends_on:
      - minio
  e2e:
    depends_on:
      - db
      - redis
      - createbuckets

MinIO로 초기 버킷 생성하기

  • createbuckets 는 AWS S3와 호환 가능한 서버인 MinIO(minio)를 실행하기 위한 컨테이너

  • E2E 테스트를 실시하기 전 필요한 초기 설정으로 앱이 사용하는 이미지를 업로드할 버킷을 생성함

  • MinIO 공식 도커 이미지인 minio/mc 를 사용하면 명령줄 인터페이스 에선 MinIO 클라이언트를 조작하는 커맨드 사용 가능

// docker-compose.e2e.yaml
services:
  db: ...
  redis: ...
  minio: ...
  createbuckets: ...
    image: minio/mc
    depends_on: 
      - minio
    entrypoint: >
      /bin/sh -c "
      /usr/bin/mc alias set myminio http://minio:9000 root password;
      /usr/bin/mc mb myminio/image --region=ap-northeast-2;
      /usr/bin/mc anonymous set public myminio/image;
      tail -f /dev/null;
      "
  e2e: ...
  • 컨테이너 시작과 함께 실행되는 entrypoint 에서 다음과 같은 MinIO 클라이언트 커맨드를 실행함

    • alias set myminio http://minio:9000 root password;

      • http://minio:9000 에는 myminio 라는 이름의 엘리어스를 만듬

    • mb myminio/image --region=ap-northeast-2;

      • myminioimage라는 이름의 버킷 생성

    • anonymous set public myminio/image;

      • image 버킷의 접근 권한을 퍼블릭으로 설정

    • tail -f /dev/null;

      • 프로세스가 종료되지 않도록 대기한다

  • docker compose up--exit-code-from 옵션은 컨테이너 중 하나만 종료돼도 프로세스를 종료한다.

    • 이 때문에 버킷 초기화를 완료한 후에도 컨테이너가 종료되지 않도록 tail -f /dev/null; 로 프로세스 유지시킴

  • 도커 컴포즈의 --exit-code-from 옵션은 특정 컨테이너의 종료 시그널을 반환받음

    • 이를 위해 npm 스크립트의 docker:e2e:cie2e 를 지정한 것

    • 이는 --abort-on-container-exit 를 내포한 컨테이너가 한 개라도 중지되면 모든 컨테이너가 중지되면서 E2E 테스트를 실시할 수 없게 된다.

    • 현재로선 특정 컨테이너의 종료 시그널을 무시하는 설정이 없기 떄문에 이와 같은 임시방편을 사용

외부 컨테이너 호스트를 E2E 컨테이너에 매핑하기

  • 깃허브 액션이 실행되는 가상 환경에서 로컬 개발 환경처럼 localhost 또는 0.0.0.0 같은 호스트 명을 사용할 수 없음

  • 대신 컨테이너명을 참조하면 컨테이너 간 통신이 가능

    • 예를들면 DATABASE_URLlocalhost 대신 db 라는 컨테이너명을 사용함

// docker-compose.e2e.yaml

version: "3"
service:
  e2e:
    build:
      context: .
      dockerfile: Dockerfile.e2e
      args:
        DATABASE_URL: postgresql://root:password@db:5432/app-db?schema=public
    environment:
      REDIS_HOST: redis
      REDIS_PORT: 6739
      AWS_S3_ENDPOINT: http://minio:9000
      AWS_ACCESS_KEY_ID: root
      AWS_SECRET_ACCESS_KEY: password
      DATABASE_URL: postgresql://root:password@db:5432/app-db?schema=public
    depends_on:
      - db
      - reids
      - createbuckets
    ports:
      - "3000:3000"

docker-compose.e2e.yaml

version: "3"
services:
  db:
    image: postgres:13.3
    environment:
      POSTGRES_USER: root
      POSTGRES_PASSWORD: password
    ports:
      - 5432:5432
  redis:
    image: redis:latest
    ports:
      - 6379:6379
  minio:
    image: minio/minio:latest
    environment:
      MINIO_ROOT_USER: root
      MINIO_ROOT_PASSWORD: password
    command: server --console-address ":9001" /data
    ports:
      - 9000:9000
      - 9001:9001
  createbuckets:
    image: minio/mc
    depends_on:
      - minio
    entrypoint: >
      /bin/sh -c "
      /usr/bin/mc alias set myminio http://minio:9000 root password;
      /usr/bin/mc mb myminio/image --region=ap-northeast-1;
      /usr/bin/mc anonymous set public myminio/image;
      tail -f /dev/null;
      "
  e2e:
    build:
      context: .
      dockerfile: Dockerfile.e2e
      args:
        DATABASE_URL: postgresql://root:password@db:5432/app-db?schema=public
    environment:
      REDIS_HOST: redis
      REDIS_PORT: 6379
      AWS_S3_ENDPOINT: http://minio:9000
      AWS_ACCESS_KEY_ID: root
      AWS_SECRET_ACCESS_KEY: password
      DATABASE_URL: postgresql://root:password@db:5432/app-db?schema=public
    depends_on:
      - db
      - redis
      - createbuckets
    ports:
      - "3000:3000"

Last updated