Хочу поделиться своим опытом реализации тестирования в монорепозитории, без затрагивания написания самих тестов ...

Допустим есть репозиторий с несколькими приложениями и папкой common, в которой находятся функционал, который используется в этих приложениях 

.
├── apps
│   ├── app1
│   │   ├── cfg
│   │   │   └── __init__.py
│   │   ├── core
│   │   │   └── __init__.py
│   │   ├── docker-compose.yml
│   │   ├── manage.py
│   │   └── tests
│   │       └── __init__.py
│   ├── app2
│   └── app3
├── common
│   └── __init__.py
├── requirements-dev.txt
└── requirements.txt

При реализации тестов хотелось реализовать следующие задачи:

  • при реализации новых фич в приложениях, запускать тестирование приложений на нескольких версиях python

  • при добавлении нового функционала в common запускать тестирование всех приложений

  • при малейшем изменении в requirements.txt или requirements-dev.txt пересобирать окружение и так же запускать тестирование всех приложений

Эти задачи можно реализовать с помощью tox, добавляем файл ./tox.ini (актуально для tox==3.27.1):

[main]
current_python_env_dir = {toxworkdir}{/}current_python_env_dir
next_python_env_dir = {toxworkdir}{/}next_python_env_dir
report_temp_dir = test{/}temp{/}
pytest_flags = --tb=no

[tox]
skipsdist = True
envlist =
    {next_python, current_python}-{app1, app2, app3}

[testenv]
recreate = False
sitepackages = False
passenv =
    FORCE_COLOR
commands =
    next_python-app1: {env:TOXBUILD:py.test apps{/}app1{/}tests --junitxml={[main]report_temp_dir}test_{envname}.xml {[main]pytest_flags}}
    current_python-app1: {env:TOXBUILD:py.test apps{/}app1{/}tests{/}smoke --junitxml={[main]report_temp_dir}test_{envname}.xml {[main]pytest_flags}}

    next_python-app2: {env:TOXBUILD:py.test apps{/}app2{/}tests --junitxml={[main]report_temp_dir}test_{envname}.xml {[main]pytest_flags}}
    current_python-app2: {env:TOXBUILD:py.test apps{/}app2{/}tests{/}smoke --junitxml={[main]report_temp_dir}test_{envname}.xml {[main]pytest_flags}}

    next_python-app3: {env:TOXBUILD:py.test apps{/}app3{/}tests --junitxml={[main]report_temp_dir}test_{envname}.xml {[main]pytest_flags}}
    current_python-app3: {env:TOXBUILD:py.test apps{/}app3{/}tests{/}smoke --junitxml={[main]report_temp_dir}test_{envname}.xml {[main]pytest_flags}}

[testenv:next_python-{app1, app2, app3}]
basepython = python3.7
envdir = {[main]next_python_env_dir}
deps =
    -Urrequirements-dev.txt

[testenv:current_python-{app1, app2, app3}]
basepython = python3.8
envdir = {[main]next_python_env_dir}
deps =
    -Urrequirements-dev.txt

Добавляем докерфайл образа в котором у нас будут проходить тестирование ./test/test_tox.Dockerfile:

FROM python3.7

WORKDIR /tox_workdir
COPY requirements*.txt tox.ini /tox_workdir/

#==================================================================================
# Ставим необходимые зависимости
#==================================================================================
RUN apt-get update && apt-get -y --no-install-recommends install \
    cabextract \
    curl \
    ......
    && apt-get clean

#==================================================================================
# Добавляем пользователя webui и далее делаем все через него
#==================================================================================
RUN addgroup --gid 1000 --system web && adduser --system --gid 1000 --uid 1000 web
RUN chown -R web /tox_workdir
USER web

#==================================================================================
# Ставим pyenv, через него устанавливаем необходимые версии python
#  и прописываем пути
#==================================================================================
RUN curl https://pyenv.run | bash \
    && echo 'export PYENV_ROOT="/home/web/.pyenv"' >> ~/.bashrc \
    && echo 'export PATH="/home/web/.local/bin:$PATH"' >> ~/.bashrc \
    && echo 'export PATH="$PYENV_ROOT/shims:$PATH"' >> ~/.bashrc \
	&& echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc

RUN . ~/.bashrc && pyenv install 3.8
RUN . ~/.bashrc && pyenv global 3.8
RUN . ~/.bashrc && pip3.8 install --upgrade cython
#==================================================================================
# Установка tox и окружений
#==================================================================================

RUN pip3 install tox==3.27.1 && pip3 install --upgrade cython
RUN . ~/.bashrc && bash -c "export TOXBUILD=true && tox"

добавляем скрипт для сборки этого образа ./test/build.sh:

#!/bin/bash
docker build -f test_tox.Dockerfile -t test_tox:latest .

таким образом, после сборки этого образа у нас в контейнере будут установлены 2 версии python и 2 папки с окружениями (с разными версиями python) в которых будут установлены все пакеты из requirements-dev.txt При запуске тестирования из контейнера, окружения пересобираться не будут

Далее для запуска тестов создадим файл ./test/docker-compose.yaml :

version: "2"

services:
  db:
    image: postgres
    environment:
      - POSTGRES_USER=root
      - POSTGRES_HOST_AUTH_METHOD=trust
    volumes:
      # инит скрипт для создания баз данных для приложений, можно положить
      # рядом, в ./test/create_databases.sql
      - ./create_databases.sql:/docker-entrypoint-initdb.d/init.sql
  cache:
    image: memcached:alpine
  test_tox:
    image: test_tox
    volumes:
      - ../:/test
      # это нужно для того, чтобы локальные конфиги заменялись конфигами,
      # которые подойдут для запуска в контейнере, можно так же положить 
      # рядом в ./test/container_cfg
      - ./container_cfg:/test/apps/app1/cfg
      - ./container_cfg:/test/apps/app2/cfg
      - ./container_cfg:/test/apps/app3/cfg
volumes:
  media:

файл ./test/create_databases.sql :

CREATE DATABASE app1_test;
CREATE DATABASE app2_test;
CREATE DATABASE app3_test;

теперь можно запускать тестирование через docker-compose, для удобства создадим файл ./test/Makefile :

# You can set these variables from the command line.
TOX_WORK_DIR=/tox_workdir/.tox
APP1_ENV=-e current_python-app1,next_python-app1
APP2_ENV=-e current_python-app2,next_python-app2
APP3_ENV=-e current_python-app3,next_python-app3
ALL_ENVS=$(APP1_ENV) $(APP2_ENV) $(APP3_ENV)

define run-test
	docker-compose rm --all
	docker-compose up -d db
	docker-compose up -d cache
	docker-compose run --rm test_tox /bin/bash -c ". ~/.bashrc && cd /test/ && tox $1 --workdir $(TOX_WORK_DIR) -q --result-json .tox-result.json" || :
	docker-compose down
	docker-compose rm --all
endef

build:
	./build_local.sh

app1_test:
	$(call run-test, $(APP1_ENV))

app2_test:
	$(call run-test, $(APP2_ENV))

app3_test:
	$(call run-test, $(APP3_ENV))

tests:
	$(call run-test, $(ALL_ENVS))

all: build tests

.DEFAULT_GOAL := all

через Makefile мы можем пересобрать тестовый контейнер и запустить тестирование интересующих нас приложений, например:

cd test
make build
make app1_test
make tests

Дерево проекта выглядит следующим образом:

.
├── apps
│   ├── app1
│   │   ├── cfg
│   │   │   └── __init__.py
│   │   ├── core
│   │   │   └── __init__.py
│   │   ├── docker-compose.yml
│   │   ├── manage.py
│   │   └── tests
│   │       └── __init__.py
│   ├── app2
│   └── app3
├── common
│   └── __init__.py
├── requirements-dev.txt
├── requirements.txt
└── test
    ├── Makefile
    ├── build.sh
    ├── container_cfg
    │   └── __init__.py
    ├── create_databases.sql
    ├── docker-compose.yaml
    └── test_tox.Dockerfile

Плюсы такой реализации:

  • Все тестирование выполняется в отдельном контейнере, который уже содержит все необходимое для запуска тестов

  • Можно очень быстро проверить работоспособность приложений, при изменении common, не переходя в папки тестов отдельных подсистем

  • Тестирование приложений с несколькими окружениями (так же можно тестировать с разными версиями определенных пакетов)

  • Наработки из папки ./test/ можно легко прикрутить в CI/CD при запуске тестирования в пайплайне

Минусы:

  • Костыльное вкорячивание 2 версии python в один контейнер

  • Долгая пересборка контейнера за счет установки второй версии python

  • Невозможность запуска тестирования с дополнительными флагами (например --pdb)

P.S. При написании статьи постарался максимально обезличить проект, так что возможны некоторые неточности, главное было донести суть.

Комментарии (2)


  1. Alexufo
    14.04.2024 00:48

    docker-compose поддерживает inline создания контейнеров dockerfile_inline, удобно, когда конфиг небольшой, но я не разобрался, как заставить каждый раз при старте docker-compose делать билд образа, он кешируется, зараза. Приходится удалять образ до запуска.


    1. pqbd
      14.04.2024 00:48
      +2

      docker compose build --no-cache