hitomedia Tech Blog

株式会社ヒトメディアのテクノロジーに関するブログです

ruby-jmeterで負荷テストをした話とTips

こんにちは。id:Tetsujinです。

ヒトメディアでは、いろいろとWebアプリケーションの開発や運用をしていますが、自社のものなのか、お客様向けに納品するのものなのか、などで作るものに求める・求められるレベル感も変わることが多く、アプリケーションの性能要件の決め方や、計る手法が確立していません。

いままでも、シンプルに測定するのはSiegeを使ったり、シナリオを組む場合はJMeterを使うこともあれば、Load Impactを契約して利用したこともありました。

また、テスト結果のレポート提出が必要な場合は、自分たちで実施せずにテスト会社様にお願いをしたりと、都度都度、状況にあわせてやる、やらない、やり方を決めていました。

最近、改めてJMeter(お客様の要望もあり)で負荷テストをすることがあり、これを機にもう少しノウハウを蓄積していこうと取り組んだので、Tipsを共有します。

ruby-jmeterについて

ruby-jmeterを使うと、RubyDSLでテストシナリオを書いてjmx(JMeterの設定ファイル)を生成することができます。

https://github.com/flood-io/ruby-jmeter

  • JMeterGUIでポチポチ操作しなくてよくなる。
  • テストシナリオがコードでバージョン管理できる。
  • いつでもテストシナリオを再生性可能であるという安心感。

ただ、結局JMeter自体の知識は必要となるので、JMeterのドキュメントは読む必要がありますし、それに対応するruby-jmeterのコードってどうなるんだっけ?とDSLの中身を追いかける必要もでてくるので、 敷居が低いのかというと、そうでは無いと感じています。

インストール方法や使い方などはインターネット上に、非常にわかりやすい記事が沢山あります。

Tips

今後も他のアプリケーションに対して、ruby-jmeterを使う時にも流用しようかなと思っているTipsです。

環境変数で環境毎の設定を変更する

direnvdotenvなどを使って環境変数で、テスト対象の環境毎の細かな設定変更をするようにしました。

JMeter的には、 user_defined_variables で変更すべき項目なのかもしれませんが、都度、設定変更するのも面倒なので環境変数で定義しておいて差し替えられるようにしています。

ローカル環境ではBASIC認証をかけていませんが、ステージング環境ではかけていたのでその切り替えや

if ENV["JMETER_AUTH_USER"]
  http_authorization_manager url: ENV["JMETER_BASE_URL"],
                             username: ENV["JMETER_AUTH_USER"],
                             password: ENV["JMETER_AUTH_PASSWORD"]
end

ステージング環境ではJMeterのクライアント側マシンのファイルパスが異なったためその切り替えや

csv_data_set_config filename: "#{ENV["JMETER_DATA_DIR"]}/data1.csv"

direnvを使って、負荷テストを実施する環境に応じてjmxを生成できるようにしています。

.envrc

export JMETER_BASE_URL="https://example.com"
export JMETER_DATA_DIR="$PWD/data"

.envrc.d/staging/.envrc:sh

export JMETER_BASE_URL="https://staging.example.com"
export JMETER_AUTH_USER="user"
export JMETER_AUTH_PASSWORD="password"
export JMETER_DATA_DIR="C:\\jmeter\\data"

Gemfile

source 'https://rubygems.org'
gem 'ruby-jmeter'

コマンドラインから生成します。

# ローカル向け
$ bundle exec ruby generate_jmx.rb

# ステージング向け
$ direnv exec .envrc.d/staging bundle exec ruby generate_jmx.rb

ロジックの共通化

今回のプロジェクトでは、シナリオ毎の共通点が多かったのでログイン処理などは一部、モジュールに切り出して共通化しました。

module/auth.rb

module Auth
  def login
    -> {
      visit name: "ログイン - 表示", url: "#{ENV["JMETER_BASE_URL"]}/login" do
        extract name: "csrf-token", xpath: '//*[@id="_token"]/@value', tolerant: true
      end

      submit name: "ログイン - 実行", url: "#{ENV["JMETER_BASE_URL"]}/login", always_encode: true,
             fill_in: {
               "LoginForm[_token]"   => "${csrf-token}",
               "LoginForm[login_id]" => "${login_id}",
               "LoginForm[password]" => "${password}",
             } do
      end
    }
  end
end

generate_jmx.rb

require 'ruby-jmeter'

require_relative 'module/Auth'
include Auth

test name: 'example' do
  threads name: "シナリオ1", count: 10, loop: 1 do
    csv_data_set_config filename: "#{ENV["JMETER_DATA_DIR"]}/data1.csv",
                        shareMode: "shareMode.group",
                        quotedData: true,
                        recycle: false,
                        stopThread: true,
                        fileEncoding: "Shift-JIS"
    cookies
    login.call
    # 以降、シナリオのコード
    # ...
  end
end.jmx(file: 'example.jmx')

あまりやり過ぎると、シナリオの見通しが悪くなるため、適度にやるのがよさそうです

テストデータの定義

負荷テストのシナリオで利用するテストデータは csv_data_set_configCSVを読み込んでいます。

csv_data_set_config filename: "#{ENV["JMETER_DATA_DIR"]}/data1.csv",
                    shareMode: "shareMode.group",
                    quotedData: true,
                    recycle: false,
                    stopThread: true,
                    fileEncoding: "Shift-JIS"

今回のプロジェクトでテストに必要なデータは、1レコードにつき100列ほど定義が必要だったため、CSVを直接管理するのは無理だ。と感じて結果的にExcel管理にしました。

もう少し良い管理方法はありそうなのですが、お客様に「このデータでテストします〜」という提示、ファイルの受け渡しも必要だったので容易さも考えてExcelで。

こんな感じでシナリオ毎のデータをシートを分けて定義しておき、CSVに変換します。 f:id:Tetsujin:20170722155227p:plainf:id:Tetsujin:20170722155235p:plain

1列目のヘッダが変数名となり、参照できるようになります。

submit name: "ログイン - 実行", url: "#{ENV['JMETER_BASE_URL']}/login", always_encode: true,
       fill_in: {
         'LoginForm[_token]'   => '${csrf-token}',
         'LoginForm[login_id]' => '${login_id}',
         'LoginForm[password]' => '${password}',
       } do
end

さいごに

というわけで、今回プロジェクトでruby-jmeterを使ってみましたが、なんでもかんでもruby-jmeterでシナリオ書いて負荷テストをするか?と言うと、そうは考えてません。

以下のような場合は、ruby-jmeterが有用だと思っています。

  • テスト対象のアプリケーションの仕様をコードレベルで把握している。
  • 使い捨てじゃなくて、継続的にテストを管理・実行していくつもりがある。

ですが、テスト対象のアプリケーションの仕様をあまり把握はしてないけどテストをすると言う場合は、無理にruby-jmeterは使わず、JMeterのHTTP Proxyをブラウザに通して、ブラウジングを記録させてシナリオを作っていた方が、効率的なんじゃないかなと思っています。

参考: http://inokara.hateblo.jp/entry/2015/08/25/060119