모두에게 믿음을 주고, 아무도 믿지 말라. 서버개발자는 늘 모든 상황을 의심하고, 대비해야 한다. 아무도 믿지 않음으로써, 믿음직한 서버를 만들어야 한다.
컴퓨터는 맹목적이다. 한 치의 의심 없이, 주인이 작성한 작성한 코드를 실행한다. 심지어 자기 자신이 잘못될지라도 말이다. 프로그램을 짜다가 컴퓨터, 혹은 다른 전자기기가 멈추어버린 식겁한 경험이 다들 있을 것이다.
만약 우리가 만든 웹 서버에 결함이 있었다면? 서버 개발에서 가장 피하고 싶은, 500 에러가 유저에게 노출되고 말 것이다. 이런 사태는 방지해야 한다. 즉, 서버가 실행되기 전에 결함을 파악할 수 있어야 한다. 하지만, 어떻게?
바로 테스트 자동화를 통해 로직을 확인하는 것이다. 이 커밋은 기존의 서버를 깨뜨리지 않습니다, 라는 사람의 말을 믿지 마라. 자동화된 테스트가 정합성을 검증하게 하고, 실패한다면 서버에 받아들일 수 없도록 하는 시스템을 구축하라.
이를 위해 github action
이나 travis ci
등의 자동화 도구를 도입하는 것이 좋다. 또한 codecov
등을 통해 테스트 커버리지 등을 확인하는 것도 좋다.
https://github.com/typelevel/cats/pull/3754 같은 예시를 살펴보자. 여러 환경에서의 테스트가 구축되어 기존 코드와의 정합성을 확인한다.
Github Action
에서지금 작업 중인 프로젝트에서는 가장 간단한 github action
을 이용해 테스트 자동화 환경을 구축했다. 이미 Docker
를 사용 중이기 때문에 테스트를 할 때에도 기존 이미지를 활용하는 쪽으로 가닥을 잡았다.
다음은 github action
의 yaml
파일을 일부 손 본 것이다.
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
을 이용한 테스트와 배포 자동화부터 시작해보자.