見出し画像

【テスト自動化】実行可能jarをテストクラスから呼び出し、標準出力の内容をみて終了させる方法

SHIFT Group 技術ブログ

はじめに

こんにちは。SHIFTのテスト自動化エンジニアの松岡です。

javaを使用したシステムでは、テスト自動化の際に実行可能jarの動作確認が必要になる場合があります。
単純なjarの起動/終了であれば、Setup/Teardownで処理することが可能ですが、今回自動化対象としたシナリオでは、

・画面操作と同時にjarの起動が必要になる
・jarは常駐プログラムのため自動では終了しない ・jar起動と同時にjarからログが標準出力で吐き出され始める
・標準出力の中に期待値があるかどうかという判断が必要
・標準出力はログファイル等には出力されない

上記のような状況でした。 そこで、「実行jarをコマンドラインから実行し、期待値となる標準出力が確認できたらCtrl+Cでjarを終了すること」をjavaで再現する方法が必要になりました。その実装の説明を行っていきたいと思います。

◆対象ケース

改めて今回のケースの概要をまとめると以下のようになります。

①GUIテスト自動化の際にjarの呼び出しが必要

②GUI操作とjar起動は同時に行う必要があり、事前にどちらかを実施することはできない

③jarのログが標準出力に吐き出され、ログの内容と標準出力の突合が必要

④標準出力の内容はログファイル等には書き出しがされない

「jar起動と同時にログが出力され始める」かつ「ログが標準出力からしか確認できない」というのがポイントです。

◆実行可能jar呼び出しjavaクラス

実装するjava全体は以下の通りです。

package foobar;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

public class JarTest {
   static final int MAX_WAIT = 60000;

   public String executeJar(String[] commands, String expectedOutput) throws IOException, InterruptedException{
      String outputs = "";
      final String LINE_SEPARATOR = System.getProperty("line.separator");
      Runtime runtime = Runtime.getRuntime();

      // 最大待機時間
      long startTime = System.currentTimeMillis();
      long maxTimeMillis = MAX_WAIT + startTime;

      Process process = runtime.exec(commands);

      try (InputStream inputStream = process.getInputStream;
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);) {
         StringBuffer output = new StringBuffer();
         String outputLine;
         while ((line = bufferedReader.readLine()) != null) {
            output.append(line + LINE_SEPARATOR);
            if (System.currentTimeMillis() >= maxTimeMillis || line.equals(expectedOutput)) {
               break;
            }
         }

         outputs = output.toString(); // 標準出力

         process.destroy();

         return outputs;
      }
   }
}

◆実装概要

以下、まとまりごとに内容を見ていきます。

 String outputs = "";
 final String LINE_SEPARATOR = System.getProperty("line.separator");

まず実行対象のjarの出力を受け取るString型の変数を初期化します。 改行は実行環境によって異なりますので、システムプロパティから取得するようにします。

// 最大待機時間
long startTime = System.currentTimeMillis();
long maxTimeMillis = MAX_WAIT + startTime;

期待値となる文字列が標準出力にあらわれない場合に、最大何秒待機するか設定します。 まず現在時刻のミリ秒を取得し、クラス変数MAX_WAITに設定した数値に加算しています。 この例では1分(60,000ミリ秒)待機しても期待値が出力されない場合、jarの実行を停止します。

Process process = runtime.exec(commands);

try (InputStream inputStream = process.getInputStream;
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);) {
   StringBuffer output = new StringBuffer();
   String outputLine;
   while ((line = bufferedReader.readLine()) != null) {
      output.append(line + LINE_SEPARATOR);
      if (System.currentTimeMillis() >= maxTimeMillis || line.equals(expectedOutput)) {
         break;
      }
   }

runtime.exec(commands)で、引数のコマンドを実行します。(jar実行コマンドの記述方法は後述)
bufferedReaderから標準出力を行ごとに取得します。(null出ない場合繰り返し処理)
なお、try-with-resource文を使用してclose()によるリソース解放を簡略化しています。ここの変数宣言でインスタンス生成をネストすると、コンストラクタの例外発生時にリソース解放されなくなるため、1行ごとに記載しています。取得した行に改行コードを付加して、StringBuffer型の変数outputに追加します。

所定の待機時間が過ぎるか、メソッドの引数で指定した期待値と等しい場合、break;で繰り返し処理を終了します。
なおここでは期待値は完全一致にしていますが、

line.equals(expectedOutput)

line.contains(expectedOutput)

にすることで、部分一致に変更することも可能です。(またはメソッドの引数を増やすなどしてカスタマイズしてください)

 outputs = output.toString(); // 標準出力

 process.destroy();

繰り返し処理が終わるまでに取得した標準出力を、returnする変数outputsの0番目の要素に格納し、bufferedReaderおよびinputStreamをクローズします。 次のprocess.destroy();で、手動でjarを実行した場合の「Ctrl + C」による終了を再現しています。
※try-with-resources文を使用しているため、finallyを使用したclose()によるリソースの解放は記載しません。

◆呼び出し例

JarTest jarTest = new JarTest();

String[] jarCommand = new String[5];
jarCommand[0] = "java";
jarCommand[1] = "-jar";
jarCommand[2] = "/foo/bar.jar";
jarCommand[3] = "arg1";
jarCommand[4] = "arg2";
String result = jarTest.executeJar(jarCommand, "foobar");

コマンドの文字列を1つ目の引数に、期待値を2つ目の引数に設定して、executeJarメソッドを呼び出します。
実行するjarへの引数は配列の要素として追加します。
この例では「/foo/bar.jar」のパスにある実行可能jarを引数arg1とarg2を引数に実行し、"foobar"が標準出力として返ってくることを期待値としています。
後続の処理で変数resultを操作すれば、期待値が返ってくるまでの間の標準出力の文字列を使用した処理を実装することもできます。


執筆者プロフィール:松岡 直人                    SIerでのJava、jqueryの開発経験を経て、SHIFTに入社。 SHIFTでは入社当初より画面/APIテスト自動化に関わる。 「人間が最低限しか頑張らない自動化」を目指している。


みんなにも読んでほしいですか?

オススメした記事はフォロワーのタイムラインに表示されます!
SHIFT Group 技術ブログ
「無駄をなくしたスマートな社会の実現」を目指し、ソフトウェア製品の開発、運用、マーケティングなどあらゆる立場から携わるSHIFT Groupの公式note。エンタメ・ゲーム業界から、Web系、金融/製造/小売りなどのエンタープライズ業界まで広い知見を活かした情報を発信しています。