JMeterのBeanShell Pre/PostProcessorを使う

# はじめに

JMeterにはいろいろなコンポーネントがあって、性能テストやテストの自動化に便利なのですが、中にはよく分からないコンポーネントも少なくないです。多分その中の代表であると思われる BeanShell Pre/PostProcessor についての話です。

※なお、この記事では、JMeter3.3を使用しました。

# BeanShellとは？

Javaのコードをスクリプトで書くことができるものらしいです。この仲間には、JSR223やGroovyなどがあります。ともかく、BeanShellを使うにはJavaを知らないければならない、ということです。

# BeanShell Pre/PostProcessorの使い方

「HTTPリクエスト」を右クリック「追加」から選択できます。PreProcessorは「前処理」、PostProcessorは「後処理」にあります。

![20180427_1.png](https://qiita-image-store.s3.amazonaws.com/0/208281/228efc7a-c78f-2fde-4378-5ea1c6e79b47.png)

基本的には、「Script」の枠にコードを書きます。「Script File」にファイル名を書くと、ファイルに書かれたコードを使うようになります。

![2018-04-27_141400.png](https://qiita-image-store.s3.amazonaws.com/0/208281/31fc6202-ea22-d885-cbd5-27b8e920a1e2.png)

# BeanShellで使える変数（予約変数）

BeanShellの一番難しいところは、何の変数（予約変数）が使えるのかが分かりにことでしょう。さらにBeanShellの種類によって使える変数が少し異なっている、というのも難しくしています（いちおう`variables`の箇所に書かれて入るのですが）。ほぼJMeterのマニュアルのコピペですが、表にまとめました。説明の欄は英語を日本語にしただけです。

各変数が使えるメソッドはjavadocを参照してください。リンクを張っておきました。

変数名|BeanShell Listener|BeanShell PreProcessor|BeanShell PostProcessor|BeanShell Timer|説明|型
---|:---:|:---:|:---:|:---:|---|---
log | ○ | ○ | ○ | ○ |ログファイルの出力に使用できる|[Logger](https://www.slf4j.org/api/org/slf4j/Logger.html)
ctx | ○ | ○ | ○ | ○ |contextへのアクセス|[JMeterContext](https://jmeter.apache.org/api/org/apache/jmeter/threads/JMeterContext.html)
vars | ○ | ○ | ○ | ○ |変数へのreadとwriteのアクセス|[JMeterVariables](https://jmeter.apache.org/api/org/apache/jmeter/threads/JMeterVariables.html)
props | ○ | ○ | ○ | ○ |JMeterのプロパティ|java.util.Properties
sampleResult | ○ | | | |前の`SampleResult`へのアクセス|[SampleResult](https://jmeter.apache.org/api/org/apache/jmeter/samplers/SampleResult.html)
sampleEvent | ○ | | | |今の`SampleEvent`へのアクセス|[SampleEvent](https://jmeter.apache.org/api/org/apache/jmeter/samplers/SampleEvent.html)
prev | | ○ | ○ | ○ |前の`SampleResult`へのアクセス|[SampleResult](https://jmeter.apache.org/api/org/apache/jmeter/samplers/SampleResult.html)
sampler | | ○ | | |今の`sampler`へのアクセス|[Sampler](https://jmeter.apache.org/api/org/apache/jmeter/samplers/Sampler.html)
data | | |  ○ | |現在の`sample data`へのアクセス|byte[]

# BeanShellの例１：Cookieを動的に付与する

Cookieは、通常スレッドグループに「HTTPクッキーマネージャ」を入れておけば、HTTPレスポンスからJMeterは自動的にCookieの処理をしてくれます。しかし、あるページがJavaScriptでCookieを付与しているなど、手動でCookieを付与したい場合がまれにあります。JMeterには「CookieマネージャPreProcessor」というものは存在しないので、BeanShellで行わなければなりません。

## BeanShell Pre/PostProcessorの書き方

```java
import org.apache.jmeter.protocol.http.control.CookieManager;  
import org.apache.jmeter.protocol.http.control.Cookie;

CookieManager manager = ctx.getCurrentSampler().getCookieManager();
Cookie cookie = new Cookie();
cookie.setName("foo");
cookie.setValue("bar");
cookie.setDomain("hoge.example.com");
manager.add(cookie);
```

内容は見たとおりです。注意点としては、

- `import`文がいる
- `PreProcessor`の場合、なぜか変数`sampler`が使えないので、`ctx.getCurrentSampler()`から取得する
- Cookie Domain名も設定しないと送信されない
- 「HTTPクッキーマネージャ」もスレッドグループに追加する必要がある（そうしないと、Cookieが保存されない）

です。JMeterの変数からCookie名や値を取得する場合は、`vars.get()`を使えば良いです（JMeterの変数にCookie値などを格納する場合は、`vars.put()`）。

## 設定箇所

「HTTPリクエスト」にPreProcessorを追加すると、リクエストを送る前に処理するので、そのリクエストでCookieを送信します。PostProcessorかListenerで追加すると、リクエストを送った後で処理をするので、レスポンスの値（ヘッダーやボディ）を元にして、Cookieを付与できます。JavaScriptでCookieを付与している場合、レスポンスのボディにあるJavaScriptを（正規表現抽出などで）パーズしてCookie値を取り出すことになるので、Listenerを使うことになると思います（PostProcessorでもいいですが、ボディの扱いがListenerのほうが簡単なため）。

# BeanShell の例２：JSONのパーズ

JMeter3では、「JSON Extractor」があるので BeanShell は不要になりましたが、BeanShell Listenerの例としてはちょうどよさそうなので、例として挙げておきます。

## BeanShell Listener

```java
import net.minidev.json.*;

var body = sampleResult.getResponseDataAsString();
log.warn(body);
var json = JSONValue.parse(body);
vars.put("hoge", json.get("error"));
```

これも見たとおりですね。注意点としては、

- JMeter3では、JSONのライブラリが変更になった。(org.json.* → net.minidev.json.*)

くらいでしょうか。

## 設定箇所

レスポンスをパーズすることになるので、当然PostProcessorかListenerになります。

# まとめ

設定で動かすツール系は、プログラムを**もろ**に書けるようなコンポーネントを使ってしまうと、内部の実装に大きく依存するため、バージョンアップやバグなどに強く影響を受けてしまうため、互換性が悪いことが多いのです。そのため、使わないで済むなら、なるべく使わないようにしたほうが良いです。しかし、JMeterで提供されていないコンポーネントについては、BeanShell を使わざるを得ないでしょう。

# マニュアル

- [JMeter Component](https://jmeter.apache.org/usermanual/component_reference.html)