Dockerを使ってLocust構築しサイトのテストをする

目次

説明

今回Dockerを使ってlocustのテスト環境を構築します。

locustを使う場合、ただリクエストを送りhtmlファイルをダウンロードするだけなので、静的ファイルはダウンロードされません。(画像、Javascript)

しかし、今回ブラウザからのリクエスト状態と同じにするため、テストシナリオの中に静的ファイルをダウンロードする、

関数も入れたいと思います。

必要ソフト

以下のソフトが必要になります。

  • Docker

始める前にインストールしておいてください。

事前準備

同じディレクトリー内に以下のファイルを作ってください。

  • Dockerfile
  • requirements.txt
  • docker-compose.yml

各ファイルの用途と内容は以下になります。

Dockerfile

コンテナの設定ファイルです。このファイルの中にコンテナ内で必要なソフトやモジュールを入れます。

内容は以下になります。

FROM locustio/locust

ADD requirements.txt .

RUN pip3 install --upgrade pip
RUN pip3 install -r requirements.txt

requirements.txt

コンテナ内に入れる具体的なモジュールのリストを入れます。

今回必要なモジュールは以下になります。

  • lxml
  • Beautifulsoup4

なので内容は以下になります。

beautifulsoup4==4.11.1
lxml==4.8.0

docker-compose.yml

docker-composeの設定ファイルです。Docker-composeはコンテナの起動や停止などを簡単に行えるツールです。

内容は以下になります。

version: '3'

services:
  master:
    build: .
    ports:
      - "8089:8089"
    volumes:
      - ./:/mnt/locust
    command: -f /mnt/locust/locustfile.py --master -H http://master:8089

  worker:
    build: .
    volumes:
      - ./:/mnt/locust
      - ./samples:/home/locust/samples
    command: -f /mnt/locust/locustfile.py --worker --master-host master

locustで行うテストのシナリオの準備

次にテストのシナリオを準備します。

docker-compose.ymlと同じディレクトリにlocustfile.pyというファイルを作ってください。
(注意:ファイル名はlocustfile.pyにしてください。違う名前の場合エラーが出ます。)

まずは必要なモジュールをインポートします。

from locust import HttpUser, task, TaskSet, SequentialTaskSet
from bs4 import BeautifulSoup
import requests
import os
import base64

次にテストシナリオを作成します。

class GetStaticFilesHttpUser(HttpUser):
    @task
    def execute_test(self):
        response = self.client.get("/")

次に静的ファイルの情報を得てコンテナ内に書き込み最後に削除する関数を作ります。
(コンテナないのフォルダをmountしているのでローカルでも(docker-compose.ymlと同じディレクトリない)作り出されて削除されます。)

具体的なステップは以下になります。

  1. 1: src属性のあるscriptタグとimgタグを取得する。
  2. 2:画像ファイルの場合画像のdata URI化の可能性もあるためdata URI化の画像の状況とrequestモジュールを使って内容を取得することができる状況二つに分ける。Data URI化の場合src属性の内容からデータをデコードし、データをファイルに書き込み最後に削除する。ファイルはsamples/imagesに書き込む
  3. 3: javascriptファイルの場合src属性のurlにrequestモジュールを使ってリクエストし、内容を取得し、samples/js内に書き込み、最後にそのファイルを削除する。
  4. 4: 1~3 を繰り返す。

画像のData URI時の追加処理関数

画像のData URI時の追加処理関数は以下になります。

    def get_file_information_for_base_sixty_four(self, url, count):
        semicolon_index = url.index(";")
        slash_index = url.index("/")
        appendix = url[slash_index+1:semicolon_index]
        filename = "{filename}.{appendix}".format(
            filename="sample_{img_index}".format(img_index=count),
            appendix=appendix
        )
        file_data = url.split('base64,')[1]
        data = {}
        data["filename"] = filename
        data["file_data"] = file_data
        return data

この関数は画像の拡張子を取得しダミーのファイル名と画像のデータを返す関数です。

画像/Javascriptデータを状況に応じて書き込む関数

データを書き込む関数は以下になります。

    def write_file(self, host_url, urls, file_type="img"):
        count = 1
        for url in urls:
            url_filename = ""
            data = None
            if "data:image" in url:
                data = self.get_file_information_for_base_sixty_four(
                    url, count)
                url_filename = data["filename"]
            else:
                url_filename = url.split("/")[-1]
            file_path = url
            if "http" not in url or "https" not in url:
                file_path = host_url + "/" + url
            if not file_path:
                continue
            save_path = IMG_PATH
            if file_type == "js":
                save_path = JS_PATH
            if not os.path.exists(save_path):
                os.makedirs(save_path)

            with open(save_path + "/" + url_filename, 'wb') as f:
                if data is None:
                    file_response = requests.get(file_path)
                    f.write(file_response.content)
                else:
                    decoded_data = base64.decodebytes(
                        data["file_data"].encode("ascii"))
                    f.write(decoded_data)
            os.remove(save_path + "/" + url_filename)
            count += 1

src属性を取得し書き込み用の関数渡す関数

src属性を取得する関数は以下になります。

    def fetch_static_assets(self, response, host_url):
        soup = BeautifulSoup(response.content, "lxml")

        img_tags = soup.find_all("img", src=True)
        img_urls = [img["src"] for img in img_tags]

        self.write_file(host_url, img_urls)

        js_tags = soup.find_all("script", src=True)
        js_urls = [js["src"] for js in js_tags]

        self.write_file(host_url, js_urls, "js")

テストシナリオ完成

これでテストシナリオは完成です。

全てのコードは以下になります。

from locust import HttpUser, task, TaskSet, SequentialTaskSet
from bs4 import BeautifulSoup
import requests
import os
import base64
IMG_PATH = "samples/images"
JS_PATH = "samples/js"


class GetStaticFilesHttpUser(HttpUser):
    def get_file_information_for_base_sixty_four(self, url, count):
        semicolon_index = url.index(";")
        slash_index = url.index("/")
        appendix = url[slash_index+1:semicolon_index]
        filename = "{filename}.{appendix}".format(
            filename="sample_{img_index}".format(img_index=count),
            appendix=appendix
        )
        file_data = url.split('base64,')[1]
        data = {}
        data["filename"] = filename
        data["file_data"] = file_data
        return data

    def write_file(self, host_url, urls, file_type="img"):
        count = 1
        for url in urls:
            url_filename = ""
            data = None
            if "data:image" in url:
                data = self.get_file_information_for_base_sixty_four(
                    url, count)
                url_filename = data["filename"]
            else:
                url_filename = url.split("/")[-1]
            file_path = url
            if "http" not in url or "https" not in url:
                file_path = host_url + "/" + url
            if not file_path:
                continue
            save_path = IMG_PATH
            if file_type == "js":
                save_path = JS_PATH
            if not os.path.exists(save_path):
                os.makedirs(save_path)

            with open(save_path + "/" + url_filename, 'wb') as f:
                if data is None:
                    file_response = requests.get(file_path)
                    f.write(file_response.content)
                else:
                    decoded_data = base64.decodebytes(
                        data["file_data"].encode("ascii"))
                    f.write(decoded_data)
            os.remove(save_path + "/" + url_filename)
            count += 1

    def fetch_static_assets(self, response, host_url):
        print("fetching")
        soup = BeautifulSoup(response.content, "lxml")

        img_tags = soup.find_all("img", src=True)
        img_urls = [img["src"] for img in img_tags]

        self.write_file(host_url, img_urls)

        js_tags = soup.find_all("script", src=True)
        js_urls = [js["src"] for js in js_tags]

        self.write_file(host_url, js_urls, "js")

    @task
    def execute_test(self):
        response = self.client.get("/")
        self.fetch_static_assets(response, self.host)

locust起動

次にdocker-compose.ymlと同じディレクトリに行き

以下のコマンド打ち環境をビルドしてください。

docker-compose build

最後に立ち上げます。

docker-compose up --scale worker=1

workerの数は状況において調整してください。

そして「http://localhost:8089/」をブラウザで開いてください。

以下の様になります。

Number of usersにユーザー数、Spawn rateにユーザーの増加速度(数/1秒になります)、HostにテストしたいURLを入れてStart Swarmingをクリックしテストしてください。

よかったらシェアしてね!
  • URLをコピーしました!
目次