Why

 

필자는 Django를 사용할때 TestCase 작성을 많이 하는데 prod서버와 같은 환경에서 테스트를 편하게(push 할때마다) 실행하고 싶었다.

 

Test Code

정말 simple한 테스트다.

github action에서 내가 만든 모델을 자동으로 DB migration 해줘야 했기 때문에 간단한 모델도 추가했다.

# models.py
from django.db import models


class Ci(models.Model):
    name = models.CharField(max_length=255)
    age = models.IntegerField()


class CD(models.Model):
    name = models.CharField(max_length=255)
    age = models.IntegerField()
    
    

# test.py
from django.test import TestCase
from dummy.models import Ci, CD


class CiTest(TestCase):
    def test_plus(self):
        self.assertEqual(1+1, 2)

    def test_minus(self):
        self.assertEqual(1-1, 0)

    def test__ci_model(self):
        name = "leemoney93"
        age = 100
        ci = Ci.objects.create(name=name, age=age)
        self.assertEqual(ci.name, name)
        self.assertEqual(ci.age, age)

    def test_cd_model(self):
        name = "leemoney93"
        age = 100
        ci = CD.objects.create(name=name, age=age)
        self.assertEqual(ci.name, name)
        self.assertEqual(ci.age, age)

 

Docker 설정

가장 기본적인 Dockerfile이다. 

requirements 의존성을 install 해준다.

# Dockerfile
FROM python:3.9

ENV PYTHONUNBUFFERED 1
ENV PYTHONDONTWRITEBYTECODE 1

# dependencies를 위한 apt-get update
RUN apt-get update && apt-get -y install libpq-dev -y

RUN apt-get install libssl-dev -y

RUN apt-get install -y netcat

RUN apt-get update \
  # dependencies for building Python packages
  && apt-get install -y build-essential \
  # psycopg2 dependencies
  && apt-get install -y libpq-dev \
  # Translations dependencies
  && apt-get install -y gettext \
  # Additional dependencies
  && apt-get install -y procps \
  # cleaning up unused files
  && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \
  && rm -rf /var/lib/apt/lists/*

# Requirements are installed here to ensure they will be cached.
COPY ./requirements.txt /requirements.txt
RUN pip install -r /requirements.txt

WORKDIR /app

 

다음은 docker-compose.yml 파일 

github action에서 test 실행 시 db 컨테이너를 사용한다.

이미지 재활용을 위해 docker hub에 있는 image를 가져와 테스트를 진행한다.

version: '3.8'

services:
  web:
    build:
      context: .
      dockerfile: ./Dockerfile
    image: <본인 docker hub ID>/ci  # 이미지 재활용을 위해 docker hub에서 pull 받아올 때 사용
    volumes:
      - .:/app
    expose:
      - 8000
    env_file:
      - ./.env  # 환경 설정 파일 사용
    depends_on:
      - db

  db:
    image: mysql:5.7
    restart: always
    environment:  # 테스트 DB
      MYSQL_DATABASE: 'ci'  
      MYSQL_USER: 'admin'
      MYSQL_PASSWORD: 'admin123'
      MYSQL_ROOT_PASSWORD: 'admin123'
      MYSQL_ROOT_HOST: '%'
    command:
      - --character-set-server=utf8
      - --collation-server=utf8_general_ci
    ports:
      - '3306:3306'
    expose:
      - '3306'

 

설정파일

docker-compose 파일에서 .env 파일로 부터 환경 설정을 한다고 했다(env_file)

.env 파일에 DB 정보를 넣어주고, django의 settings 파일에 해당 정보를 사용했다. 

# .env
SQL_ENGINE=django.db.backends.mysql
SQL_DATABASE=ci
SQL_USER=root
SQL_PASSWORD=admin123
SQL_HOST=db
SQL_PORT=3306
DATABASE=mysql

# settings.py
DATABASES = {
    'default': {
        "ENGINE": os.environ.get("SQL_ENGINE", ""),
        "NAME": os.environ.get("SQL_DATABASE", ''),
        "USER": os.environ.get("SQL_USER", ""),
        "PASSWORD": os.environ.get("SQL_PASSWORD", ""),
        "HOST": os.environ.get("SQL_HOST", ""),
        "PORT": os.environ.get("SQL_PORT", ""),
        "OPTIONS": {"charset": "utf8mb4"},
    }
}

 

 

Github action 설정

 

필자는 매 push 시 Test를 하고 싶다고 했다. 그러기 위해선 몇가지 스텝이 있다. 하나씩 살펴보자.

 

1. 매번 이미지를 build하면 시간이 너무 오래걸린다. image pull 하자.

(해당 step은 Login ~ Pull까지)

자세한 내용은 https://github.com/marketplace/actions/docker-login 참고)

2. Test 전에 mysql db가 활성화됐는지 확인해야 된다. 해서 django command에 wait_for_db를 명령을 추가해준다

(아래 wait_for_db 참고)

3. Test 실시

name: Checks

on: [push]

jobs:
  test-lint:
    name: Test
    runs-on: ubuntu-18.04
    steps:
      - name: Login to Docker Hub
        uses: docker/login-action@v1
        with:
          username: ${{ secrets.DOCKERHUB_USER }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}
      - name: Checkout
        uses: actions/checkout@v2
      - name: Pull
        run: docker-compose pull
      - name: wait
        run: docker-compose run web python manage.py wait_for_db
      - name: Test
        run: docker-compose run web python manage.py test

 

 

Wait_for_db

 

import time

from django.core.management.base import BaseCommand
from django.db.utils import OperationalError


class Command(BaseCommand):
    def handle(self, *args, **kwargs):
        self.stdout.write('waiting for db ...')
        db_up = False
        while db_up is False:
            try:
                self.check(databases=['default'])
                db_up = True
            except OperationalError:
                self.stdout.write("Database unavailable, waiting 1 second ...")
                time.sleep(1)

        self.stdout.write(self.style.SUCCESS('db available'))

 

 

맺음말: 파이썬은 실수하기 쉬운언어다. 가능한 많은 테스트케이스를 만들자. 그리고 github action을 사용해 테스트 자동화를 구축하자.

코드 github 주소 : https://github.com/seunwoolee/djangoci

+ Recent posts