Open8

Docker x Django x Selenium でE2Eテスト環境を構築する方法(ARM版対応)

HiroHiro

開発環境

以下の開発環境で動作確認を行いました。

  • OS: macOS Sonoma 14.7

  • チップ: Apple M2

  • Docker: 28.3.2

  • Docker Compose: 2.38.2

  • Python: 3.13.2

  • Django: 4.2.19

  • PostgreSQL: 17.5

  • Selenium: 4.34.2(Docker 経由、開発時のlatest版)

    前提

  • Dockerを使用し、Djangoアプリケーションを開発している。

  • Djangoのsettingsにおいて、開発環境と本番環境を分けている。

  • docker-compose.yml を用いたコンテナ構成を採用している。

  # docker-compose.ymlの例
services:
  db:
    image: postgres
    platform: linux/amd64
    volumes:
      - db-data:/var/lib/postgresql/data
    ports:
      ...
  web:
    ...
    environment:
      - DJANGO_SETTINGS_MODULE=config.settings.local  # ローカル環境用設定
   ...
  
volumes:
  db-data:
HiroHiro

docker-compose.test.yml の作成

docker-compose.test.ymlには、既存のdocker-compose.yml に追記、あるいは上書きしたい内容を記述します。

# docker-compose.test.yml
services:
  db:
    volumes:
      - db-data-test:/var/lib/postgresql/data  #  【重要】開発環境と別のボリューム名を使用
  web:
    environment:
      - DJANGO_SETTINGS_MODULE=config.settings.test

  # Selenium サーバーを起動するコンテナ
  selenium:
    # image は、以下のいずれかを選択する。
    image: seleniarm/standalone-chromium  # M1 Mac など ARM用のイメージ
    # image: selenium/standalone-chrome  # intelチップなど

    environment:
      - VNC_NO_PASSWORD=1  # パスワード入力なし

    ports:
      - "4444:4444"  # Selenium WebDriver接続用
      - "5900:5900"  # VNC で接続するポート
      - "7900:7900"  # ブラウザで確認を行うためのポート

    # コンテナが使用するメモリの上限
    shm_size: "2gb"

volumes:
  db-data-test:  # 【重要】開発環境と別のボリューム名を使用
  1. 【重要】テストで使用するデータベースに関して、ボリューム名は開発環境とは別にします。
  2. web (Djangoアプリのコンテナ)にテストで使用する設定ファイルを定義(次のセクションで作成)します。
  3. Seleniumに関する設定は、services の中に記述します。
  4. 採用するイメージは、お使いのPCのチップによって選択します。
  • M1 MacなどApple Silicone製チップ -> seleniarm/standalone-chromium
  • intel製チップ -> selenium/standalone-chrome
  1. selenium のその他の設定については、記述のとおりとします。
  2. volumes において、dbコンテナで定義したボリューム名を記述します。
HiroHiro

テスト用設定ファイル(test.py)の作成

Djangoの設定ファイルについて、以下のようなディレクトリ構造であることを前提とします。

config
└── settings
  ├── __init__.py
  ├── base.py        # 共通設定
  ├── local.py       # 開発環境用
  ├── production.py  # 本番環境用
  └── test.py        # テスト用(このセクションで作成)

はじめに config/settings/ ディレクトリ配下に test.py ファイルを新規作成します。
test.py には、開発用環境用の local.py の内容をコピペします。
test.py で確実に変更すべきポイントは、データベースの名前です。

DATABASES = {
    "default": {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'django_test',  # ✅️ テスト用のDBは開発用と分ける
        'USER': <任意の値>,
        'PASSWORD':  <任意の値>,
        'HOST':  <任意の値>,
        'PORT':  <任意の値>,
    }
}
HiroHiro

E2Eテストで共通するテストケースを作成

E2Eテストは、StaticLiveServerTestCase を継承したテストケースを作成して行います。
以下のコードは、共通する内容を記述した core アプリにE2Eテスト用の共通テストケースを定義した例です。

# core/tests/e2e.py
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from selenium import webdriver
from selenium.webdriver.common.by import By


class BaseStaticLiveServerTestCase(StaticLiveServerTestCase):
    """
    Selenium を用いた E2E テストで使用するクラス。

    Attributes:
        driver (selenium.webdriver): テスト用のブラウザドライバ。
    """
    host = 'web'  # ✅️ Djangoアプリのコンテナ名

    # fixturesの設定は任意
    fixtures = ['accounts/fixtures/socialapp.json']  # 外部認証サービスを使用した例

    @classmethod
    def setUpClass(cls):
        super().setUpClass()  # super() は最初に記述
        cls.driver = webdriver.Remote(
            command_executor='http://selenium:4444/wd/hub',
            options=webdriver.ChromeOptions()
        )
        cls.driver.implicitly_wait(10)  # 読み込みの待機時間

    @classmethod
    def tearDownClass(cls):
        cls.driver.quit()
        super().tearDownClass()  # super() は最後に記述

この設定の肝となる部分は、host = 'web' の部分です。
この設定がされていない場合、ブラウザ上にテスト実行の様子が表示されませんので、忘れずに設定します。
この 'web' はDjangoアプリのコンテナの名前ですので、docker-compose.yml で使用している名前に合わせて変更してください。
その他の設定については、上記内容をそのまま使ってください。

HiroHiro

データベースの新規作成と noVNCの起動

これでE2Eテストを行う下準備が整ったので、まずは Selenium のイメージを準備します。

# コンテナの停止
$ docker compose stop

# イメージのビルド
$ docker compose up --build

次にテスト用データベースの作成を行います。

# コンテナの停止
$ docker compose stop

# テスト用dbコンテナの起動
$ docker compose -f docker-compose.yml -f docker-compose.test.yml up -d --wait db

# dbコンテナに入りテスト用データベースを作成
$ docker compose exec db
$ psql -U postgres  # USER が postgresの場合の例
$ create database django_test;  # django_test という名前のデータベースを新規作成

このあと exit コマンドを2回入力してコンテナを抜けます。

テストの準備が概ね完了したので、ブラウザを起動し、localhost:7900にアクセスします。

localhost:7900 にアクセス

"接続" をクリックし、下図のように表示されたらnoVNCの起動成功です。

localhost:7900 接続後

この localhost:7900 は、Dockerで起動した Seleniumコンテナが持つVNCサーバーのWeb UI です。
ブラウザからアクセスすることで、実際に自動テストが動く様子をGUIで確認できます。

HiroHiro

E2Eテストで表示させるURLにアクセスする方法

E2Eテストでは、実行中のSeleniumブラウザがアクセスするURLを明示的に指定する必要があります。

Djangoで StaticLiveServerTestCase を使っている場合、Django側が提供している self.live_server_url をベースにURLを指定します。

例えば、開発環境でlocalhost:8000/accounts/login/ でアクセスできるログインページがあったとします。Seleniumを経由するテスト環境では、ログインページを以下のように指定します。

base_url = self.live_server_url
self.driver.get(f'{base_url}/accounts/login/')

このように、self.live_server_url で ホスト・ポートを含めたベースURL を取得し、そこにパス(例: /accounts/login/)をつなげることで、対象のページにアクセスできます。

この self.live_server_url は、テスト実行時にDjangoが自動で立ち上げるテストサーバーのURLを表します。


これでテストの環境構築は概ね完了です。
Django公式Docなどを参考にテストメソッドを作成して実行することができます。

なお、コンテナを停止・削除するには、以下のコマンドを入力します。

# コンテナの停止・削除
$ docker compose down --remove-orphans
HiroHiro

Seleniumブラウザのスクリーンサイズとウィンドウサイズの設定方法

Seleniumブラウザで表示されるスクリーンサイズは、docker-compose.test.yml で環境変数を設定することで変更可能です。
例えば、スクリーンの幅を 1600px、高さを1200px にしたい場合は以下のように設定します。

services:
  db:
    ...

  # Selenium サーバーを起動するコンテナ
  selenium:
    ...
    environment:
      ...
      # スクリーン設定
      - SCREEN_WIDTH=1600  # スクリーン幅
      - SCREEN_HEIGHT=1200  # スクリーン高さ
      # - SCREEN_DEPTH=24  # カラービット深度  # 基本的に変更する必要はない
    ...

次にスクリーンに描画されるウィンドウサイズを変更します。
前述の共通テストケースを定義した core/tests/e2e.pysetUpClass() で、次のように記述することでウィンドウサイズを変更できます。

# core/tests/e2e.py
class BaseStaticLiveServerTestCase(StaticLiveServerTestCase):
    ...
    @classmethod
    def setUpClass(cls):
        super().setUpClass()  # super() は最初に記述
        cls.driver = webdriver.Remote(
            command_executor='http://selenium:4444/wd/hub',
            options=webdriver.ChromeOptions()
        )
        cls.driver.implicitly_wait(10)  # 読み込みの待機時間

        # ウィンドウサイズ設定
        cls.driver.maximize_window()  # スクリーン内で最大化する場合
        # cls.driver.set_window_size(1280, 1024)  # 任意のサイズで表示する場合(例: 1280 x 1024px)

これらの設定を反映させるために一度、コンテナを停止して、再起動します。

# コンテナの停止
$ docker compose stop

# コンテナの再起動
$ docker compose -f docker-compose.yml -f docker-compose.test.yml up -d
HiroHiro

【参考】Makefile によるコマンドの簡略化について

docker-compose.yml をオーバーライドするコマンドや、データベースの初期設定を行うコマンドは複雑なコマンドであるため、Makefile にショートカットのようにコマンドを定義すると効率的にテストを実行できます。

まずは、プロジェクト直下にMakefile という名前でファイルを作成します(拡張子は不要)。

これまで使用したコマンドを例にMakefileにコマンドを定義すると以下のようになります。

.PHONY: help init-test-db up-test test up down

help:
	@echo "make コマンドリスト:"
	@echo "  make init-test-db   # 初期化 & テストDB作成"
	@echo "  make up-test        # テスト用コンテナ起動"
	@echo "  make test           # 全体テスト"
	@echo "  make up             # 開発用コンテナ起動"
	@echo "  make down           # コンテナ停止・削除"

init-test-db:
	docker compose stop &&\
	docker compose -f docker-compose.yml -f docker-compose.test.yml up -d --wait db &&\
	docker compose exec db psql -U postgres -c "create database django_test;" &&\
	docker compose stop

up-test:
	docker compose stop &&\
	docker compose -f docker-compose.yml -f docker-compose.test.yml up -d

test:
	docker compose exec web python manage.py test

up:
	docker compose up -d

down:
	docker compose down --remove-orphans

1行目の .PHONY は、ファイルではなくコマンドであることを明示的に定義しているものです。

Makefile に定義したコマンドを実行するには、次のようにmake に続けてコマンドを入力します。

# ヘルプの表示
$ make help

# データベースの初期化
$ make init-test-db

# テスト用コンテナの起動
$ make up-test
...

このように一つのコマンドで複数の動作が行えるので、必要なコマンドをご自身で追加してテスト等の効率化を図ることができます。