SPAにおける負荷テストの問題

SPA(Single Page Application)は、JavaScriptで実装されるWebクライアントアプリケーションです。そのため自動テストを行うためにはブラウザーを制御する必要があります。

自動テストを用いて負荷テストを実施する場合、たくさんのシナリオ実行エージェントを起動する必要があります。

つまり、SPAの負荷テストを行う場合は、たくさんの独立したテスト用ブラウザーを同時に起動する必要があり、ツールの開発には手間がかかります。

今回は、記事タイトルの通り、Pythonとseleniumを使ってテストシナリオを実装し、Dockerでヘッドレスブラウザーを起動して大量に並行実行できるようにする方法を解説します。

尚、完成品はgithubに公開しています。

テストシナリオの実装

まずはpythonのseleniumモジュールをインストールします。

$ pip install selenium

今回はpythonのモジュール名をstress_testingとしますので、プロジェクトディレクトリstress_testingを作成し、その中にもソースコード配置用に同じ名前のディレクトリを作成します。pythonモジュールのセットアップファイルとしてsetup.pyを作成し、テストシナリオの実装場所としてsuite.pyを作成します。

ファイルツリー

stress_testing/
├── setup.py
└── stress_testing
    ├── __main__.py
    └── suite.py

setup.py

pythonモジュールの定義を記述します。

from setuptools import setup, find_packages

setup(
    name="stress-testing",
    version='1.0',
    description='Stress testing automation bot',
    packages=find_packages(),
)

__main__.py

実行時に最初に呼び出される場所です。環境変数の取得やパラメータの処理を行います。

import stress_testing.suite
import os


def main():
    os.environ.setdefault("BOT_HEADLESS", "False")
    os.environ.setdefault("BOT_TARGET", "https://starter-angular.moaiapps.net")

    headless = os.environ.get("BOT_HEADLESS") == "True"
    url = os.environ.get("BOT_TARGET")
    login_id = os.environ.get("BOT_LOGIN_ID")
    password = os.environ.get("BOT_PASSWORD")
    operation = os.environ.get("BOT_OPERATION")

    stress_testing.suite.start(
        headless, url, login_id, password, str(operation))


if __name__ == "__main__":
    main()

suite.py

テストシナリオを実装するファイルです。seleniumを利用してブラウザの操作を行います。

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import time
import datetime


def printlog(log):
    now = datetime.datetime.utcnow()
    timestamp = now.strftime('%Y-%m-%d %H:%M:%S')
    print(f'{timestamp}: {log}', flush=True)


def start(headless, url, login_id, password, operation):
    printlog(f'Suite started login_id={login_id}, operation={operation}')

    options = Options()
    if headless:
        options.add_argument('--headless')
        options.add_argument('--no-sandbox')
        options.add_argument('--disable-dev-shm-usage')

    driver = webdriver.Chrome(options=options)

    ops = operation.split('+')
    for op in ops:
        printlog(f'operation {op}')
        if op == 'Login':
            cmd_login(driver, url, login_id, password)
        elif op == 'Logout':
            cmd_logout(driver, url)

    time.sleep(5)

    driver.quit()
    printlog('Quit')


def cmd_login(driver: webdriver.Chrome, url, login_id, password):
    printlog('Opening login page')
    driver.get(url + '/login')

    login_id_f = driver.find_element('name', 'loginId')
    login_id_f.send_keys(login_id)

    password_f = driver.find_element('name', 'password')
    password_f.send_keys(password)

    button_f = driver.find_element('css selector', 'button[type=submit]')
    button_f.click()

    printlog('Attempting login')
    time.sleep(3)


def cmd_logout(driver: webdriver.Chrome, url):
    printlog('Attempting logout')
    driver.get(url + '/logout')
    time.sleep(3)

試しに実行してみる

ここまで実装すれば実際にChromeを操作する処理の確認ができます。

プロジェクトのルートディレクトリで以下を実行してください。軽いアプリなので一瞬で終わってしまいますがw

bash$ BOT_LOGIN_ID=testuser BOT_LOGIN_PASSWORD=testpass1! python -m stress_testing

多重起動スクリプトの実装

上記で作成したボットエージェントをDocker Imageにして、Containerとして大量起動することで負荷テストを実現できます。

DockerではGUIがありませんので、ヘッドレスモードを使用します。

そしてシェルスクリプトでCSV定義ファイルを読み込んで順次コンテナを起動する処理を実装していきます。

ファイルツリー

ここで追加するファイルは以下のとおりです。

stress_testing/
├── Dockerfile
└── batch
    ├── bot-list.csv
    └── raise.sh

Dockerfile

最初に作成したPythonモジュールを実行するためのDocker Imageを作成するファイルです。テスト実行用のブラウザとしてchromeのインストールも行います。

FROM debian:bullseye-slim

RUN apt update \
  && apt install -y python3 python3-pip wget gnupg \
  && wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | apt-key add - \
  && wget -q https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb \
  && apt install -y ./google-chrome-stable_current_amd64.deb \
  && apt clean \
  && rm -rf /var/lib/apt/lists/ \
  && rm google-chrome-stable_current_amd64.deb

RUN pip3 install selenium chromedriver-binary~=$(/usr/bin/google-chrome --version | perl -pe 's/([^0-9]+)([0-9]+\.[0-9]+).+/$2/g')

WORKDIR /opt/app
COPY . .

ENV BOT_HEADLESS True
ENV PYTHONUNBUFFERED 1
CMD [ "python3", "-m", "stress_testing" ]

bot-list.csv

実行するコンテナのパラメータ定義ファイルです。operation列で、実行する処理に変化を加えられるようにしました。

login_id,password,operation
testuser,password1!,Login
testuser,password1!,Login+Logout

raise.sh

これまで定義してきたものを実際に実行するbashスクリプトです。

まずDocker Imageを作成し、それからCSVを読んで順次コンテナを起動していく処理内容となっています。

各コンテナは処理が終わると消えてしまうため、ログを残したい場合はdocker runのオプションから--rmを削除してください。

#!/bin/bash

PWD=`pwd`
SCRIPT_DIR=$(cd $(dirname $0) && pwd)
PROJECT_ROOT=$SCRIPT_DIR/..

BOT_TARGET=https://starter-angular.moaiapps.net

if [ "$1" != "" ]; then
  BOT_TARGET="$1"
fi

echo BOT_TARGET=$BOT_TARGET

on_error() {
  echo "error $@"
  exit 1
}

cd $PROJECT_ROOT
docker build -t moaiapps/stress-testing:1.0 . || on_error docker build

SKIP=1
for i in $(cat $SCRIPT_DIR/bot-list.csv); do
  SKIP=$(($SKIP-1))
  if [ $SKIP -ge 0 ]; then continue; fi
  LOGIN_ID=$(echo $i | cut -d ',' -f 1 | sed "s/'/\\\'/g")
  PASSWORD=$(echo $i | cut -d ',' -f 2 | sed "s/'/\\\'/g")
  OPERATION=$(echo $i | cut -d ',' -f 3)
  ENV="-e BOT_TARGET=$BOT_TARGET -e BOT_LOGIN_ID='$LOGIN_ID' -e BOT_PASSWORD='$PASSWORD' -e BOT_OPERATION=$OPERATION"
  docker run -itd $ENV --rm moaiapps/stress-testing:1.0 || on_error docker run
done

まとめ

サンプルプロジェクトの説明は以上です。スクレイピングなどにも応用できそうですが、相手サイトに負荷をかけることになるため応用にはご注意ください。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です