Thanks Driven Life

日々是感謝

ruby-jmeter 使ってみました

JMeter 大好きな皆さんこんばんは。日頃から JMeter 触ってますか? 僕はそんなに触ったことないんですが、 ruby-jmeter というものを見つけたので試しに触ってみました。

はじめに

  • この記事は ruby-jmeter 2.1.2 (2013/10/14) を想定しています。
  • 結局は本家見るに越したことないので、おかしいところとか最新が知りたい方は是非本家へ

ruby-jmeter って?

README から引用

Tired of using the JMeter GUI or looking at hairy XML files?

ということでお疲れの貴方達のために Ruby で TestPlan 書けるようにしました、っていうやつです。

まずは実行してみる

インストール 済んだらこんなコードを書いてみます。

# -*- coding: utf-8 -*-
require 'ruby-jmeter'

test name: 'テスト計画' do
  threads name: 'スレッド' do
  end
end.jmx(file: 'example1.jmx') # default "jmeter.jmx"

これを実行 bundle exec ruby example1.rb すると example1.jmx が作成されますので、jmeter で開きましょう。

$ jmeter -t example1.jmx

開くとこのようなテスト計画になっているでしょう。Ruby のコードと照らし合せてみると如何でしょうか。直感的ではないでしょうか。

f:id:gongoZ:20131014204930j:plain

ちなみに end.jmx の所を end.run とすると $PATH 上に jmeter があれば jmeter が起動します。README に詳しく書いている のでこちらでは割愛。

いろんなコンポーネントを使ってみる

Sampler や Controller、Assertion といった JMeter の各種コンポーネントは それぞれ設定値を持たせることができて、通常 JMeter 画面でポチポチ入力していくのですが、 ruby-jmeter では Hash で渡していくのが基本となります。

例えば

  • スレッドは3つ
  • http://localhost:8500 にアクセスする
  • ↑ の Response に //*[@id='hogefuga'] が存在することを確認
  • Summary Report もついでに主力したい!

みたいなテスト計画を ruby-jmeter で書くとこんな感じ

# -*- coding: utf-8 -*-
require 'ruby-jmeter'

test name: 'テスト計画2' do
  threads name: 'スレッド!', count: 3 do
    visit url: 'http://localhost:8500' do
      xpath_assertion(xpath: '//*[@id="hoge"]')
    end
  end

  summary_report
end.jmx(file: 'example2.jmx')

こいつから生まれた example2.jmx はこんな感じです。

f:id:gongoZ:20131014204933g:plain

基本はまず :nameコンポーネント名として使われ、残りのキーが各コンポーネント固有のものとなります。

余談

上記コードで xpath_assertionvisitdo ; end の外に書いた場合、 xpath_assertionvisit が同レベルとして JMeter に扱われます。

  threads name: 'スレッド!', count: 3 do
    visit url: 'http://localhost:8500'
    xpath_assertion(xpath: '//*[@id="hoge"]')
  end

f:id:gongoZ:20131014215017j:plain

コンポーネントを親子関係として持たせたい場合は do ; end に閉じ込める、と覚えておくと良いでしょう。

コンポーネントの設定値の指定をするには

では Hash のキーは何を使えばいいのというと、以下の順番で調べていくと捗ると思います。

  1. 本家 README
  2. 本家 Example
  3. ruby-jmeter/DSL.md
  4. ruby-jmeter/dsl 以下の各種ファイル

スレッドグループや GET/POST なんかは README や Examples でだいたい補完できますが、 先程の例であげた xpath_assertionsummary_report は、今のところ地道に ruby-jmeter の中身を追っていくことになります。が、そこまで難しくないので大丈夫そう。

たとえば BSF PreProcessor を使いたいと思ったとき

  • まずは README 読む → 無い
  • Example も見てみよう → 無い
  • DSL.md を見てみる → なるほど BSF PreProcessorruby-jmeter だと bsf_preprocessor として扱っているんだな
  • 確かに ruby-jmeter/dsl/bsf_preprocessor.rb があった
  • で、結局キーはなんなの → XML 見よう → なんとなく使いたい言語は name="scriptLanguage" で、コードは name="script" に書くっぽい!!

というわけで書いてみる

# -*- coding: utf-8 -*-
require 'ruby-jmeter'

test name: 'テスト計画3' do
  bsf_preprocessor(scriptLanguage: 'javascript', script: "vars.put('foobar', 'hoge');")
end.jmx(file: 'example3.jmx')

結果

f:id:gongoZ:20131014204936j:plain

こんな感じで。最初はめんどくさいんですが、どのコンポーネントもだいたい同じアプローチで実現できると思います。結局のところ XML の Prop name とキーが対応しているって覚えとけばどうにかなる。

余談

ruby-jmeter/dsl.rbdsl/ 以下にあるコンポーネントの alias 作りまくってます。 本来はスレッドグループは thread_group() ってメソッドで実装されているんですが、 README や Example にもあるとおり、threads() でアクセスできるように dsl.rb の中でいろいろやっています。 自分が使いたいメソッドが見つかった場合、そのまま使わずにまずは dsl.rb の中で検索してみると便利 alias があるかもしれません。

今できなそうなこと

collectionProp とかなんかネストした要素にアクセスするのがめんどい。

例えば UserParameters の XML って

<UserParameters guiclass="UserParametersGui" testclass="UserParameters" testname="#{testname}" enabled="true">
  <collectionProp name="UserParameters.names"/>
  <collectionProp name="UserParameters.thread_values">
    <collectionProp name="1"/>
  </collectionProp>
  <boolProp name="UserParameters.per_iteration">false</boolProp>
</UserParameters>

みたいな感じになっているんですが、ここで

names / user user 1 user 2 user 3
username taro hanako gongo
password orat okanah ognog

というユーザーパラメータを設定したいと考える。これまでのように name がキーに対応するなら

user_parameters(names: ['username', 'password'],
                thread_values: [['taro', 'hanako'],
                                ['orat', 'okanah'],
                                ['gongo', 'ognog']])

って書くのかな?と思ったんですが実際に生成された XML

<UserParameters guiclass="UserParametersGui" testclass="UserParameters" testname="UserParameters" enabled="true">
  <collectionProp name="UserParameters.names">["username", "password"]</collectionProp>
  <collectionProp name="UserParameters.thread_values">[["taro", "hanako", "gongo"], ["orat", "okanah", "ognog"]]</collectionProp>
  <boolProp name="UserParameters.per_iteration">false</boolProp>
</UserParameters>

みたいな感じになっちゃいます。何か回避策があるのかな、と思ったんですが今のところ対応するような実装は無さそうでした。

ちょっとぐだぐだなんですがとりあえず手元では以下のように対処しています

# -*- coding: utf-8 -*-
require 'ruby-jmeter'

def make_string_props(arys)
  arys.map do |v|
    "<stringProp name=\"#{v}\">#{v}</stringProp>"
  end.join
end

def make_collection_props(col)
  col.map do |u|
    '<collectionProp name="0">' + make_string_props(u) + '</collectionProp>'
  end.join
end

def make_user_parameters(params)
  prefix = '//collectionProp[@name="UserParameters.'
  names = params[:names]
  users = params[:users]
  first_user = users.shift

  ret = [
         {
           xpath: "#{prefix}names\"]",
           value: make_string_props(names)
         },
         {
           xpath: "#{prefix}thread_values\"]/collectionProp",
           value: make_string_props(first_user)
         }
  ]

  ret << {
    xpath: "#{prefix}thread_values\"]",
    value: make_collection_props(users)
  } unless users.empty?

  ret
end

test name: 'テスト計画3' do
  user_parameters(update_at_xpath: make_user_parameters(
      names: ['username', 'password'],
      users: [
        ['taro', 'hanako'],
        ['orat', 'okanah'],
        ['gongo', 'ognog']]
      ))
end.jmx(file: 'example4.jmx')

f:id:gongoZ:20131014215741j:plain

うーん、我ながらひどい。ま、がんばればできます、という報告でした。

余談

update_at_xpath を使うと、指定した xpath:value: を埋め込んでくれます。

さきほどの make_user_parameters を出力すると

[
    {
      :xpath => "//collectionProp[@name=\"UserParameters.names\"]",
      :value => "<stringProp name=\"username\">username</stringProp><stringProp name=\"password\">password</stringProp>"
    },
    {
      :xpath => "//collectionProp[@name=\"UserParameters.thread_values\"]/collectionProp",
      :value => "<stringProp name=\"taro\">taro</stringProp><stringProp name=\"hanako\">hanako</stringProp><stringProp name=\"gongo\">gongo</stringProp>"
    },
    {
      :xpath => "//collectionProp[@name=\"UserParameters.thread_values\"]",
      :value => "<collectionProp name=\"0\"><stringProp name=\"orat\">orat</stringProp><stringProp name=\"okanah\">okanah</stringProp><stringProp name=\"ognog\">ognog</stringProp></collectionProp>"
    }
]

こんな感じ。これを使っていけば、UserParameters みたいに 標準でサポートしてないコンポーネント設定も自前でラップしていい感じでかけるのではないでしょうか。

まとめ

クセがあるようでなかなかとっつきづらいのですが、 慣れてくるとこれまでマウスでポチポチと操作してた JMeter の設定を Ruby で完結できるのはかなりいい。 なにがいいって結局は Ruby なのである意味なんでもできるといったところじゃないでしょうか。

自社プロダクトでは単体テスト結合テストはだいぶ充実してきたものの、負荷テストが抜けていたので ruby-jmeter で補完していければいいかなと思います。

体育の日なので 、「テストしないといけないね」っていう話題にまとめました。