아무도 믿지 마라: 테스트 자동화

구경회·2021년 1월 31일
3
post-thumbnail
post-custom-banner

모두에게 믿음을 주고, 아무도 믿지 말라. 서버개발자는 늘 모든 상황을 의심하고, 대비해야 한다. 아무도 믿지 않음으로써, 믿음직한 서버를 만들어야 한다.

컴퓨터는 맹목적이다. 한 치의 의심 없이, 주인이 작성한 작성한 코드를 실행한다. 심지어 자기 자신이 잘못될지라도 말이다. 프로그램을 짜다가 컴퓨터, 혹은 다른 전자기기가 멈추어버린 식겁한 경험이 다들 있을 것이다.


만약 우리가 만든 웹 서버에 결함이 있었다면? 서버 개발에서 가장 피하고 싶은, 500 에러가 유저에게 노출되고 말 것이다. 이런 사태는 방지해야 한다. 즉, 서버가 실행되기 전에 결함을 파악할 수 있어야 한다. 하지만, 어떻게?

테스트 자동화

바로 테스트 자동화를 통해 로직을 확인하는 것이다. 이 커밋은 기존의 서버를 깨뜨리지 않습니다, 라는 사람의 말을 믿지 마라. 자동화된 테스트가 정합성을 검증하게 하고, 실패한다면 서버에 받아들일 수 없도록 하는 시스템을 구축하라.

자동화 도구를 이용하기

이를 위해 github action이나 travis ci등의 자동화 도구를 도입하는 것이 좋다. 또한 codecov등을 통해 테스트 커버리지 등을 확인하는 것도 좋다.
https://github.com/typelevel/cats/pull/3754 같은 예시를 살펴보자. 여러 환경에서의 테스트가 구축되어 기존 코드와의 정합성을 확인한다.

Github Action에서

도커를 활용하여

지금 작업 중인 프로젝트에서는 가장 간단한 github action을 이용해 테스트 자동화 환경을 구축했다. 이미 Docker를 사용 중이기 때문에 테스트를 할 때에도 기존 이미지를 활용하는 쪽으로 가닥을 잡았다.

다음은 github actionyaml 파일을 일부 손 본 것이다.

jobs:
  # Run tests.
  # See also https://docs.docker.com/docker-hub/builds/automated-testing/
  test:
    runs-on: ubuntu-20.04

    steps:
    - uses: actions/checkout@v2

    - name: create dotenv
      run: echo "${{ secrets.DOTENV }}" > project/.env

    - name: Log into GitHub Container Registry
      run: echo "${{ secrets.TOKEN }}" | docker login https://docker.pkg.github.com -u ${{ github.actor }} --password-stdin

    - name: docker build
      run: docker-compose build

    - name: test
      run: docker-compose run test

docker-compose 파일은 다음과 같다.

version: "3.7"

services:
  # 테스트를 위한
  test:
    build:
      context: .
      dockerfile: Dockerfile
    environment:
      - DJANGO_SETTINGS_MODULE=settings.${DEPLOY_STAGE:-dev}
    env_file:
      - project/.env
    working_dir: /root
    command: python manage.py test --keepdb
  # 배포를 위한
  rest-server:
    build:
      context: .
      dockerfile: Dockerfile
    environment:
      - DJANGO_SETTINGS_MODULE=settings.${DEPLOY_STAGE:-dev}
    image: docker.pkg.github.com/<path_to_image>
    ports:
      - "8000:8000"
    env_file:
      - project/.env
    working_dir: /root

도커 파일은 아래와 같다.

FROM docker.pkg.github.com/<path_to_image>
WORKDIR /root
ADD project .
RUN pip install -r ./requirements-dev.txt

위와 같은 체계를 통해 테스트 자동화를 구축했다. 이를 통해 충분히 많은 테스트 케이스가 있다면, 적어도 그 시스템들은 안전하다는 것을 보일 수 있다.

도커를 활용하지 않고

이번에는 이미지 없이, github action 컨테이너 상에서 바로 테스트를 실행해보자. 우선 localhost를 이용해야 하므로, 환경에 CI용 파일을 하나 만들어주자.

# settings/ci.py
from .base import *

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": "github-actions",
        "USER": "postgres",
        "PASSWORD": "postgres",
        "HOST": "localhost",
        "PORT": "5432",
    },
}

그리고 이제 액션을 수정해주자.

test:
    runs-on: ubuntu-20.04

    services:
      postgres:
        image: postgres
        env:
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: github-actions
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432

    steps:
    - name: checkout code
      uses: actions/checkout@v2

    - name: Cache dependency # caching dependency will make our build faster.
      uses: actions/cache@v2 # for more info checkout pip section documentation at https://github.com/actions/cache
      with:
        path: ~/.cache/pip
        key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
        restore-keys: |
          ${{ runner.os }}-pip-
    - name: create dotenv
      run: echo "${{ secrets.DOTENV }}" > snulife/.env

    - name: Setup python environment # setting python environment to 3.x
      uses: actions/setup-python@v2
      with:
        python-version: '3.8.5'
        
    - name: Install dependencies
      run: pip install -r requirements.txt

    - name: migrate and test
      run: |
        python manage.py migrate
        python manage.py test
      env:
        DJANGO_SETTINGS_MODULE: settings.ci

이미 도커허브에 존재하는 postgres 이미지를 컨테이너에서 실행하고, health check를 한다. 이 과정을 거치지 않으면 데이터베이스가 실행되기도 전에 테스트를 돌리려고 하고, 따라서 실패한다. 위 코드는 PostgreSQL을 위한 것이지만 다른 데이터베이스도 위와 비슷하게 사용할 수 있다.

- name: Cache dependency # caching dependency will make our build faster.
  uses: actions/cache@v2 # for more info checkout pip section documentation at https://github.com/actions/cache
  with:
    path: ~/.cache/pip
    key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
    restore-keys: |
      ${{ runner.os }}-pip-

이 부분은 pip 의존성 관리에서 캐시를 사용한 것이다. 당연히 속도를 높이기 위함이다.

- name: migrate and test
  run: |
    python manage.py migrate
    python manage.py test
  env:
    DJANGO_SETTINGS_MODULE: settings.ci

마지막으로, 마이그레이션과 테스트를 진행한다. 세팅 모듈 지정을 위해서 DJANGO_SETTINGS_MODULE: settings.ci라고 환경변수를 주었는데, 이 부분은 자신의 프로젝트에 맞게 적절히 수정해주자.

맺으며

단순히 테스트 자동화를 하면, 시스템의 안정을 보장할 수 있을까? 답은 아니오이다. 테스트를 아무리 많이 짜더라도, 그 시스템 자체의 안정성을 보장할 수는 없는 일이다.
테스트는 귀납법에 닿아있다. 즉, 내가 아침마다 해가 뜬다는 것을 수없이 많이 확인하더라도 내일 아침에 해가 뜰지는 알 수 없다는 것과 동일하다.

이 소프트웨어가 안전하냐 그렇지 않느냐를 좀 더 직접적으로 확인하려면 좀 더 다른 수준의 논의, 즉 타입 그물에 관한 이야기가 필요하다. 하지만 정적 타입을 도입하는 것은 그리 단순한 논의가 아니다. 언어를 바꾸는 것, 프레임 워크를 바꾸는 것, 더 나아가 조직원 전체의 인식과 사고 구조를 바꾸어야 하는 일일 수 있기 때문이다.

이에 대한 논의는 일단 미루어두자. 작은 걸음으로, github action을 이용한 테스트와 배포 자동화부터 시작해보자.

profile
즐기는 거야
post-custom-banner

0개의 댓글