2011年11月5日土曜日

FEST+JavaScript で半自動 Swing テスト

あ〜、暇だから、Swing フォームを JavaScript で動かすやり方でも考えてみっかな。

====

例えば、打鍵+目視に基づいた手動画面テストが既に実施されていたり、チェックリストとかテスト仕様書が出来ている場合に、作業負荷を減らすために、後から部分的に自動化を導入して、半自動テストにしたい場合がある。

ところでfest という、Swing アプリを Javaコードから機能テストするための API セットがあり、Javaコード からUIイベントを発生させて画面上のコンポーネントを操作するAPIが一揃い入っている(AWT の Robot をベースにしたものらしい)。

また、JavaScript コードを Javaプログラムから動かす仕組みは、Java 1.6 から備わっていて、特に何か特別なライブラリを必要とせずに、簡単に使えるようになっている。

これらを利用すれば、テスト対象フォームの外で JavaScript を実行して、フォーム上の要素を制御することができるはず。

■ 実験台

実験台として、以下のフォームを考えてみる。

こんな仕様
  • テキストボックスの名前は "field1"
  • チェックボックスの名前は "check1"
  • ボタンの名前は "button1" で、押下すると "check1"がチェックされている場合は field1 を大文字化した文字列、チェックされていない場合は field1 そのままの文字列を、標準出力に書き出す。

実験台だから単純なフォームにしたけど、実際の現場での開発対象フォームは入力項目が何十個もあって、実行ボタンを押すまでに必要な打鍵動作が多く、1回なら問題ないけど、同じようなテストケースや回帰テストをしたりすると心身ともに疲れたりする。やはり、そういうのは、何とかして省力化したい。

■ 方針

まず、テスト対象フォームを開くと同時に、以下のような別ウィンドウを開く。

で、ここで JavaScript を入力して Excecute ボタンを押下すると、手動の打鍵と同等の JavaScript が実行されるようにしたい。

■ 実装

JavaScript 入力用のウィンドウ ScriptInputWindow は、最初にテスト対象ウィンドウの FrameFixture を受け取る。

public static void main(String[] args) {
   JFrame frame = showWindow(new TestTargetWindow("Test Target"), 0);
   FrameFixture window = new FrameFixture(frame);
   showWindow(new ScriptInputWindow(window), 3000);
}
public static JFrame showWindow(JFrame frame, int xpos) {
   frame.setSize(250, 150);
   frame.setLocation(xpos, 0);
   frame.setVisible(true);
 
   return frame;
}
見ての通りテスト対象ウィンドウは TestTargetWindow だけど、ScriptInputWindow と TestTargetWindow は同じテストハーネスのプロセスから実行する事になる。もしかすると、開発対象システムに固有な何か特別な親ウィンドウとか、ランチャーとかを介さないと、テスト対象ウィンドウ単体ではフォームを開けないと心配する人もいるかもしれないが、大抵の場合は instrumentation ベースのモックツールを使えば何とかなるし、そんなに難しくもない。

ScriptInputWindow はまず最初に、テスト対象フォーム上のコントロールを、JavaScript 用の ScriptEngine に登録する。ScriptEngine も ScriptInputWindow が保持しておく。

public ScriptInputWindow(FrameFixture window) {
   super("JavaScript input window");

   initializeControlls();  

   this.jsEngine = new ScriptEngineManager().getEngineByName("JavaScript");
   registerComponents(window, window.component(), 0);
}

@SuppressWarnings("restriction")
private void registerComponents(
      FrameFixture window, Container container, int indent) {

   for (Component component: container.getComponents()) {
      String name = component.getName();
      if (null != name) {
         ComponentFixture comp = selectComponent(window, name);
         if (null != comp) this.jsEngine.put(name, comp);
      }
      if (component instanceof JComponent)
            listComponents(window, (Container)component, indent + 1);
   }
}
selectComponent() は ContainerFixture から name に一致する ComponentFixture を選んでくるメソッドだけど、面倒なので省略。

ボタンを押すと書き込んだJavaScriptコードを実行する部分は以下のようなもの。

executeButton.addActionListener(new ActionListener() {
   public void actionPerformed(ActionEvent arg0) {
      ExecutorService e = Executors.newSingleThreadExecutor();
      e.execute(new Runnable() {
         public void run() {
            try {
               String text = ScriptInputWindow.this.textArea.getText();
               ScriptInputWindow.this.jsEngine.eval(text);
            } catch (Exception e) {
               e.printStackTrace();
             }
          }
      });
   }
});

■ 試行

以下のコードを書き込んで実行すると、テキストボックスの内容が "123abcABC" に変わり、チェックボックスがチェックされ、ボタンが押され、標準出力に”123ABCABC”が出力される。
field1.setText("123abcABC");
check1.check();
button1.click();

■ 応用

  • フォーム上のコントロールの操作以前に、まずコントロールの名前を知ってなきゃならないんだけど、必要な時に階層状に表示してテスターに見せる機能もあった方が良いかもしれない。これは registerComponents でやってる事をそのままどっかに書き出すだけだから簡単。
  • 自動打鍵の結果として得られた画面上の更新項目を、比較しやすい形式でどこか(TextArea とかクリップボードでもファイルでも)に書き出す仕組みもあると便利かもしれない。グリッドとかツリーとかあると、ちょっと手間がかかるかも。
  • 自動打鍵後に、用意しておいた期待値と自動的に比較する仕組みがあると、かなり全自動に近づいてくる。
  • 手動打鍵の記録なんかまでやるとなると、それは難しい。商用製品でも買った方がマシかもしれない(下記の参考記事をみるとFESTでも対応作業中みたいな書きっぷりだけど、どうなったのか不明)。

■ 参考サイト

0 件のコメント:

コメントを投稿