JMeter 大好きな皆さんこんばんは。日頃から JMeter 触ってますか? 僕はそんなに触ったことないんですが、 ruby-jmeter というものを見つけたので試しに触ってみました。
はじめに
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 のコードと照らし合せてみると如何でしょうか。直感的ではないでしょうか。
ちなみに 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
はこんな感じです。
基本はまず :name
がコンポーネント名として使われ、残りのキーが各コンポーネント固有のものとなります。
余談
上記コードで xpath_assertion
を visit
の do ; end
の外に書いた場合、
xpath_assertion
と visit
が同レベルとして JMeter に扱われます。
threads name: 'スレッド!', count: 3 do visit url: 'http://localhost:8500' xpath_assertion(xpath: '//*[@id="hoge"]') end
各コンポーネントを親子関係として持たせたい場合は do ; end
に閉じ込める、と覚えておくと良いでしょう。
コンポーネントの設定値の指定をするには
では Hash のキーは何を使えばいいのというと、以下の順番で調べていくと捗ると思います。
- 本家 README
- 本家 Example
- ruby-jmeter/DSL.md
- ruby-jmeter/dsl 以下の各種ファイル
スレッドグループや GET/POST なんかは README や Examples でだいたい補完できますが、
先程の例であげた xpath_assertion
や summary_report
は、今のところ地道に
ruby-jmeter の中身を追っていくことになります。が、そこまで難しくないので大丈夫そう。
たとえば BSF PreProcessor
を使いたいと思ったとき
- まずは README 読む → 無い
- Example も見てみよう → 無い
- DSL.md を見てみる → なるほど
BSF PreProcessor
は ruby-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')
結果
こんな感じで。最初はめんどくさいんですが、どのコンポーネントもだいたい同じアプローチで実現できると思います。結局のところ XML の Prop name とキーが対応しているって覚えとけばどうにかなる。
余談
ruby-jmeter/dsl.rb で dsl/
以下にあるコンポーネントの 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')
うーん、我ながらひどい。ま、がんばればできます、という報告でした。
余談
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 で補完していければいいかなと思います。
体育の日なので 、「テストしないといけないね」っていう話題にまとめました。