Docker コンテナ内で起動している Python アプリケーションの自動ユニットテスト環境を構築する

# Docker コンテナ内で起動している Python アプリケーションの自動ユニットテスト環境を構築する

[前回の記事](https://qiita.com/Yu-Chihuahua/items/3a84b320633a88f3662b)で、Docker コンテナ内の Webアプリケーション を Window 環境の VSCode からデバッグできるようにしました。
今回は、テストレポートおよびカバレッジレポートを出力できる、自動ユニットテスト環境を構築したいと思います。

## 今回のゴール
- Docker コンテナ内で起動している Python アプリケーションの自動ユニットテスト環境を構築する
- テストレポート（HTML形式／JUnit XML形式／Text形式）およびカバレッジレポート（HTML形式）を出力する

## ディレクトリ構成
今回のディレクトリ構成は下記のようになります。
追加／修正対象のファイルのみを記載し、他は省略します。
```diff_shell:WSL2 Ubuntu
  /home/appuser

  ├── workspace
  │   ├── docker-compose.yaml
  │   └── web_app003
  │       ├── Dockerfile
  │       └── opt
+ │           ├── pytest
+ │           │  ├── run_pytest.sh
+ │           │  └── reports
+ │           │       └ {yyyyMMddHHmmss}
  │           └── web_app
  │               └── src
+ │                   ├── pytest.ini
+ │                   └── tests
+ │                       └── test_web_app_001.py
～～～～～～～～～～ 以下省略 ～～～～～～～～～～
```

## 手順
### 資材作成
1. ```docker-compose.yaml```ファイルを修正
    ```docker-compose.yaml```ファイルを下記のように修正します。
    - テストレポートをローカル（Ubuntu）のディレクトリと同期するように```volumes```を追加
    ```yaml:docker-compose.yaml
    version: '3'
    services:

      ##############################################################################
      # Web Application container（Python ＋ Flask ＋ Vue.js ＋ Vuetify）
      ##############################################################################
      web_app003:
        build: "./web_app003/"
        command: "/bin/bash" # Flask アプリケーションが自動的に起動しないようにする設定
        tty: true
        volumes:
          - "./web_app003/opt/pytest/reports/:/opt/pytest/reports/" # ローカル(Ubuntu)のディレクトリと同期する
        ports:
          - "5000:5000" # Flask port
          - "5173:5173" # Vue.js port
          - "5678:5678" # Debug port
        image: "web_app003:develop"
        container_name: "web_app003"
    ```
    <br>

1. ```Dockerfile```ファイルを修正
    ```Dockerfile```ファイルを下記のように修正します。
    - pytest 関連のライブラリをインストール対象に追加
    - テストレポートの出力ディレクトリを追加
    ```shell:Dockerfile
    # Amazon Linux 2023
    FROM public.ecr.aws/amazonlinux/amazonlinux:2023

    # ミドルウェアをインストール
    RUN dnf install -y \
        util-linux \
        shadow-utils \
        vim-enhanced-2:9.* \
        sudo-1.9.* \
        python3.11-3.11.* \
        python3.11-pip-22.3.* \
        python3.11-devel-3.11.* \
        nodejs-1:18.*

    # Python のライブラリをインストール
    RUN pip3.11 install flask==3.0.*
    RUN pip3.11 install flask_socketio==5.3.*
    RUN pip3.11 install flask-cors==4.0.*
    RUN pip3.11 install flask-wtf==1.2.*
    RUN pip3.11 install debugpy==1.8.*
    RUN pip3.11 install pytest==8.1.*
    RUN pip3.11 install pytest-cov==4.1.*
    RUN pip3.11 install pytest-html==4.1.*
    RUN pip3.11 install pytest_env==1.1.*

    # ホストOS と同じユーザーを追加する
    # 1000 : host OS $(id -g)
    RUN groupadd -g 1000 appuser
    # 1000 : host OS $(id -u)
    RUN adduser -m appuser -u 1000 -g appuser
    # Add SUDO group.
    RUN groupadd sudo
    # Add SUDO authority.
    RUN usermod -aG sudo appuser

    # 資材を配置する
    WORKDIR /opt/web_app/
    RUN mkdir -p /opt/web_app/
    COPY ./opt/web_app/ /opt/web_app/
    COPY ./opt/flask_start.sh /opt/
    RUN mkdir -p /opt/pytest/reports/
    COPY ./opt/pytest/ /opt/pytest/

    # オーナーとパーミッションを変更する
    RUN chown appuser:appuser /opt/web_app -R
    RUN chmod 775 /opt/web_app -R
    RUN chown appuser:appuser /opt/pytest -R
    RUN chmod 775 /opt/pytest -R

    # vite で Vue.js 環境を構築してプロダクションビルドする
    WORKDIR /opt/web_app/frontend/
    RUN npm install
    RUN npm run build

    # 起動時のコマンドを設定する
    CMD ["sh","/opt/flask_start.sh"]
    ```
    <br>

1. テストコードファイルを作成
    テストコード```test_web_app_001.py```ファイルを下記のように作成します。
    ```python:test_web_app_001.py
    # coding: UTF-8

    import sys
    sys.path.append("/opt/web_app/src/")

    import importlib
    import app_controller


    def test_web_app_001_xxxxx():
        """
        自動テストのサンプル
        """
        try:
            _app = importlib.reload(app_controller)
            index_html = _app.get_index_html()
            assert index_html == "index.html"

        except AssertionError as ae:
            raise ae

        except Exception as e:
            msg = str(e)
            assert False, f"Exception occurred. {msg}"
    ```
    <br>

1. pytest 設定ファイルを作成
    pytest 設定ファイル```pytest.ini```を下記のように作成します。
    ```ini:pytest.ini
    [pytest]
    env =
        TZ=Asia/Tokyo
        AppEnv=TEST
    ```
    <br>

1. テストレポートディレクトリを作成
    権限を appuser にする都合上、テストレポートディレクトリを先に作成しておきます。
    ```shell:WSL2 Ubuntu
    mkdir -p /home/appuser/workspace/web_app003/opt/pytest/reports
    ```
    <br>

1. pytest 起動シェルを作成
    pytest 起動シェル```run_pytest.sh```を作成します。
    ```shell:run_pytest.sh
    #!/bin/bash

    # pytest をカバレッジレポートを出力するモードで起動するシェルスクリプト
    datetime=$(date '+%Y%m%d%H%M%S')
    path=/opt/pytest/reports
    out_path=$path/$datetime

    sudo -u appuser mkdir -p $out_path/report_text/
    sudo -u appuser mkdir -p $out_path/report_xml/
    sudo -u appuser mkdir -p $out_path/report_html/
    sudo -u appuser mkdir -p $out_path/coverage_html/

    cd /opt/web_app/src/
    sudo -u appuser python3.11 -m pytest -c pytest.ini -s -v \
        --junitxml=$out_path/report_xml/report.xml \
        --html=$out_path/report_html/report.html \
        --cov-report=html:$out_path/coverage_html/ \
        --cov=/opt/web_app/src/ \
        --cov-branch /opt/web_app/src/ > $out_path/report_text/report.txt
    ```

### 動作確認
1. Docker Compose でビルド＆起動
    Docker Compose でカスタムイメージをビルドして起動します。
    ```shell:WSL2 Ubuntu
    cd /home/appuser/workspace
    docker-compose build
    docker-compose up -d
    ```
    <br>

1. 起動した Docker コンテナに入る
    ```docker exec```コマンドで、起動した Docker コンテナに入ります。
    ```shell:WSL2 Ubuntu
    docker exec -it web_app003 bash
    ```
    <br>

1. pytest を起動する
    シェルを実行して pytest を起動します。
    ```shell:web_app003
    sh /opt/pytest/run_pytest.sh
    ```
    <br>

1. テストレポートを確認する
    Windows 環境から、出力されたテストレポートを確認します。
    ```\\wsl$\Ubuntu-22.04\home\appuser\workspace\web_app003\opt\pytest\reports\{yyyyMMddHHmmss}```に下記のテストレポートが出力されています。
    - カバレッジレポート（HTML形式）：```coverage_html\index.html```
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3767768/95b2ded5-d2ed-085c-731d-0c4fa7e4bd9c.png)

    - テストレポート（HTML形式）：```report_html\report.html```
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3767768/2297761d-1824-2e72-35b2-312442a203af.png)

    - テストレポート（JUnit XML形式）：```report_xml\report.xml```
    - テストレポート（Text形式）（デフォルト）：```report_text\report.txt```

## 最後に
今回はコンテナ内にテストレポート出力を行いましたが、更に応用すれば、サーバで自動ユニットテストを定期起動し、テスト結果を公開するといったことも可能です。
特に、カバレッジレポートを視覚的に確認できるのは、重宝できるかと思っています。