カテゴリー「swing」の10件の記事

2010/05/16

FEST-Swingを利用する(8) 視覚の威力

今回は総集編、回想回です…

過去FEST-Swingに関して記述したエントリを以下に示します。

また、上記エントリ中の参考リンクの他、以下のサイトでも日本語でFEST-Swingについて解説されています。

 

これら2エントリでは、冒頭でGUIテスティング自動化の必要性について示されています。これに補足して、私のGUIテスティング自動化のモチベーションは

  • GUIが嫌いだ

ということにも起因します。

GUIテストを実施するに当たって、入力を人間が手作業で行うと、必然的に出力の取得も人間が行う必要があり、結果として入出力データの妥当性確認も手作業になります。テスト入出力データ(いわゆる”エビデンス”)の管理も煩雑化し、本当にテストできているのかどうかが曖昧になってしまいます。テスト作業にしても、Excelから拾った似たようなデータを同じ入力欄に入力し同じボタンを押して…と、全く面白みのない単純作業です。これが、ソフトウェアの変更が必要になる度に重なっていきます。

具体例を挙げます。下の動画はさきゅばすの画面で入力した値が正しくコンフィグに保存されるかどうかのテスト(の一部)をFEST-Swingで実行したものです。

小規模ソフトウェアのほんの一機能をテストするためだけでもこれだけの操作が必要です。業務システムでは何回のマウスクリックやタイピング、何種類のファイルの確認が必要になることでしょうか。

GUIテスティングの自動化が達成できれば、上記のようなソフトウェア開発に取って本質的でないコスト(私に言わせれば”不本意な状況”)を改善することができます。

テストの入力データはJUnitで実行するのと変わらず、バージョン管理システムでソフトウェア本体と同期して管理することができます。出力データはすぐにテストを実行し取得することができます。出力値だけでなくテスト実行手順すらエビデンスとして保存できます。

また、テストの作成と実行作業が単調ではなくなります。動画を見てみてどうだったでしょうか。自動で画面が操作される様子を見るだけでも興味がわいてこないでしょうか。FEST-Swingによるテストは、下記引用にあるJUnitのグリーンバーに相当する威力があると考えています。

不思議なもので、グリーンバーが表示されるととてもうれしい気分になり、いつの間にかゲーム感覚でグリーンバーを取得しようと必死になってしまいます。そういった状況に深くはまってしまう症状をテスト熱中症と呼ぶのですが、実際にテストにはまってしまう人の気持ちが分からないではありません。グリーンバーの威力は絶大です。
テストファーストによるソフトウェア開発の衝撃(後篇) – IBM developerWorks

 

次回、第6回で”次回説明する”と書いてそのままになっていたSwingのスレッドアクセスバイオレーションの話も残っているのですが、適当に作ったさきゅばすの拡張部分が結構バグっていたことが判明してしまった(参考)ので、これを題材に、モックを利用したテストケースの作成を行っていきたいと考えています。

2010/01/21

FEST-Swingを利用する(7) EDTで発生した例外をJUnitで検知するの追補

EDTで発生した例外のハンドラをカスタム化するにはsun.awt.exception.handlerプロパティを使用する、と以前記載した

先日リリースされたFEST-Swing 1.2a4では、カスタム化するためのクラスAWTExceptionHandlerInstallerが用意されたようだ。こちらを使用する方が良いかもしれない。ただし、実装上は、内部で上記プロパティを利用しているので差異は無い。

これに関連してJavaのBug ID:4714232 RFE: replace sun.awt.exception.handler system property with official API の存在を知ったのだが、Java7ではこのプロパティを用いる方法を採らなくても良くなるらしい(EDTも通常のスレッド同様setUncaughtExceptionHandlerメソッドを使えばよくなる?)。

 

追記:

現在のバージョンでも特定の条件下以外はsetUncaughtExceptionHandlerで問題ないようだ。特定の条件というのは、モーダルダイアログが表示されている状態であり、この状態では設定した例外ハンドラが使用されない、というバグらしい。下記のコードは、1.6.0_14-b08では確かにハンドラが用いられていない。1.7.0-ea-b78では想定通りの動作となった(関連バグ BugID:6727884 Some Uncaught Exceptions are no longer getting sent to the Uncaught Exception Handlers より)。

 
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
 
public class MyFrame extends JFrame {
    public MyFrame() {
        super();
        setTitle("メインウィンドウ");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JButton b = new JButton("起動");
        b.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                JDialog d = new JDialog(MyFrame.this, "モーダルダイアログ");
                d.setModal(true);
                JButton b2 = new JButton("例外");
                b2.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        throw new RuntimeException("例外送出");
                    }
                });
                d.add(b2);
                d.pack();
                d.setVisible(true);
            }
        });
        add(b);
        pack();
    }
 
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new MyFrame().setVisible(true);
 
                // EDT例外ハンドラの設定
                Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
                    public void uncaughtException(Thread t, Throwable e) {
                        System.out.println("キャッチされなかった例外:" + e.getMessage() + "(" + t.getName() + ")");
                    }
                });
            }
        });
    }
}

2009/11/05

FEST-Swingを利用する(6) 非EDTでのSwingアクセスを検証する

Swingのスレッドポリシーに記載されている通り、Swingコンポーネント及び関連クラスはイベントディスパッチスレッド(EDT)で実行する必要がある。

スタンドアロンなプログラムを作成している場合には、上記リンク先に記載されているとおり、あまり意識する必要がない。しかし、クライアント/サーバ間で非同期通信を行うプログラムなどでは、よく誤ったコーディングを見かける。

本エントリでは、上記問題の簡単な実例と、それに対するFEST-Swingでの検証方法について記述する。

 

サンプルの説明

今回のサンプルコードはこちらになる。本サンプルコードは、GUIを持つクライアント(client.Client)とサーバ(server.Server)が登場する。

クライアントは、サーバに要求を行い、サーバからその結果を受けた際に、画面に結果を表示する機能を持つ。

サーバは、クライアントからの要求を受け付けた後、非同期で処理を行い結果を通知する機能を持つ。

cs_sequence

(余談。JUDE/Community 改め astash* community、最近のバージョンでは、印刷やコピー時に、上図のような透かしが入るようになったようだ。あくまで試用版、という色合いが濃くなったということか。JUDEは2009年末までダウンロード提供、とのこと。)

 

注目すべき箇所

結果を表示するラベル(resultLabel)に文字列を設定している2箇所に注目する。

1箇所目はclient/Client.javaの106行目requestButtonActionPerformed メソッド中になる。要求ボタンを押した際に実行される。

   1: private void requestButtonActionPerformed(java.awt.event.ActionEvent evt) {                                              
   2:     resultLabel.setText("要求中...");
   3:     server.request();
   4: }

2箇所目は同ファイル134行目 listen メソッド中になる。サーバからの通知を受けた際に実行される。

   1: public void listen(String str) {
   2:     resultLabel.setText(str);
   3: }

どちらも同じようにイベントハンドラの中でラベルに文字列を書いており、実行してみても通常問題は発生しないため、このような実装が行われているのをよく見かける。

しかし実際は、一方は問題ないが、もう一方は冒頭記述のSwingスレッドポリシーに反している。

1点目のメソッドはJButton#addActionListenerメソッドで登録したハンドラであり、これは、ボタンが押された場合にEDTで実行されることが保証されている。

一方、2点目のメソッドはサーバ側のタイミングで実行される(今回の場合はサーバスレッドが実行する)。従って非EDTで実行されてしまっている。

 

FEST-Swingを用いた検出

FEST-Swingでは、上記のようなコーディング誤りを検知する方法を提供している。FailOnThreadViolationRepaintManagerというクラスがこれに当たる。

利用するには、JUnitのテストクラスの@BeforeClassで以下の1行を追加すればよい。

   1: @BeforeClass
   2: public static void setUpClass() throws Exception {
   3:     FailOnThreadViolationRepaintManager.install();
   4: }

サンプルコードのテストケースを実行してみると、例外EdtViolationExceptionが発生し

Exception in thread "Server Thread" org.fest.swing.exception.EdtViolationException: EDT violation detected

から始まる例外スタックトレースによって、前述2点目のハンドラに問題があることが検知できる。

ただし、JUnit自体は正常終了している。これは、前回記載した事項と同様で、JUnitを失敗させるためには、JUnitスレッドで例外を送出する必要がある。

実際のプログラムでは、あらゆるスレッドやプロセス(通常、クライアントとサーバは別VMだろう)で発生した例外をJUnitスレッドに集約することはできないだろう。ただし、ユニットテスト時、このようなテストにはモックオブジェクトを使用するのが通例だと考える。従って、モックオブジェクトのスレッドで発生した例外を、前回のようにJUnitスレッドに教えるコードを追加すれば正しく問題を検知できるテストコードが作成できると考える。

FailOnThreadViolationRepaintManagerを用いた検証は完璧ではない(これについては次回記載予定)のだが、十分役に立つのではないだろうか。

2009/10/13

FEST-Swingを利用する(5) EDTで発生した例外をJUnitで検知する

前回で実際のソースコードを用いた例が終了してしまい、NetBeansもあまり関係なくなったのでタイトルを変更した。

EDTで例外が発生すると、Swingのデフォルト動作では、エラー出力にトレースを出力し、EDTを再起動させるようになっているようだ。

アプリケーションを稼動させている場合だと、自動で復旧してくれるこの動作は、有難い状況が多い。しかし、ユニットテスト中はエラーが発生したことを検知できず問題の発見が遅れる場合がある。ここでは、デフォルト動作を変更し、JUnit実行時にEDTで例外が発生した場合でもJUnit上でテスト失敗として出力する方法を述べる。

今回のサンプルはこちら

 

JUnitスレッドで実行されるメソッドで発生する例外

public class MyClass {

    public void myMethod() {
        throw new UnsupportedOperationException("未実装");
    }
}

上記のようなクラスに対して、下記のJUnitテストケースを作成する。

public class MyClassTest {

    @Test
    public void testMyMethod() {
        new MyClass().myMethod();
    }
}

このテストケースを実行すると、myMethodメソッドで例外が発生するため、JUnitでは想定どおり失敗とみなされる。

 

EDTで実行されるメソッドで発生する例外

次に、EDTで例外を発生させてみる。ボタンの押下イベントハンドラbuttonActionPerformedで例外を発生させるコードを記述する。

public class MyFrame extends javax.swing.JFrame {
...
    private void initComponents() {
...
        button.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                buttonActionPerformed(evt);
            }
        });
...
    }

    private void buttonActionPerformed(java.awt.event.ActionEvent evt) {                                       
        throw new UnsupportedOperationException("未実装");
    }
...
}

これに対するJUnitテストケースを作成する。

public class MyFrameTest {
...
    @Test
    public void testOkButton() throws Throwable {
        frame.button().click();
    }
}

このテストケースを実行すると、Exception in thread "AWT-EventQueue-0"から成るスタックトレースが標準エラー出力に表示されるが、テストケース自体は正常で完了する。つまり、JUnitの結果を見ただけでは、コードに問題がないのかどうか判断できないということになる。

 

問題点の解消その1 異なるスレッドで発生した例外を取得する

JUnitスレッドとイベントディスパッチスレッド(EDT)は異なるスレッドであるため、try-catchではもちろん捕捉できない。今回は静的変数edtThrowableを用いてEDTで発生した例外をJUnitスレッドに受け渡すことにした。

public class MyFrameTest {
    private static Throwable edtThrowable;
...
    @Before
    public void setUp() {
        edtThrowable = null;
...
    }

    @Test
    public void testOkButton()  {
        frame.button().click();
        Thread.yield();
        if (edtThrowable != null) {
            throw new RuntimeException(edtThrowable);
        }
    }
...
}

テストケースの最後にedtThrowableに値がセットされているか確認し、セットされていればそれをJUnitスレッドで送出することで、JUnitテストケースを失敗させる。

次に、EDTで例外が発生した際に、この変数に例外情報をセットするコードを記述する。

 

問題点の解消その2 EDT例外時動作を変更する

EDTで例外が発生した状況をデバッガで追っていくと、EventDispatchThread.javaのhandleExceptionメソッドに以下のようなコメントが見つかる。

Handles an exception thrown in the event-dispatch thread.

If the system property "sun.awt.exception.handler" is defined, then when this method is invoked it will attempt to do the following:

  1. Load the class named by the value of that property, using the current thread's context class loader,
  2. Instantiate that class using its zero-argument constructor,
  3. Find the resulting handler object's public void handle method, which should take a single argument of type Throwable, and
  4. Invoke the handler's handle method, passing it the thrown argument that was passed to this method.

これに沿ったクラスEDTExceptionHandlerを作成し、@BeforeClassでハンドラの設定を行う。このクラスのhandleメソッドで、前述の静的変数edtThrowableに設定を行っている。

public class MyFrameTest {
    public static class EDTExceptionHandler {

        public void handle(Throwable ex) throws Throwable {
            edtThrowable = ex;
            throw ex;
        }
    }

    @BeforeClass
    public static void setUpClass() throws Exception {
        System.setProperty("sun.awt.exception.handler", EDTExceptionHandler.class.getName());
    }

    @AfterClass
    public static void tearDownClass() throws Exception {
        System.setProperty("sun.awt.exception.handler", "");
    }
...
}

 

改良したテストケースの実行

上記で作成したテストケースを実行すると、JUnitは想定どおり失敗を通知する。

 

補足

  • 上記はコードを単純化するために、マルチスレッドプログラミングについてはあまり考慮していない。JUnitスレッド側で実行しているThread.yieldで確実にEDTが実行されるとは限らず、また、edtThrowableへは両スレッドがアクセスするため同期化が必要だろう。
  • 実際に私がコーディングしていた際には、前準備や後処理は抽象クラスにまとめ、それを継承した具象テストクラスを作成していた。ただ、この方法をとると、NetBeansが自動で実行してくれるテストでは、抽象クラスも実行しようとしてしまい、テストメソッドがないと怒られるようだ。

 

参考

2009/10/07

NetBeansでFEST-Swingを利用する その4 テストケースの更新

ここでは、現在のテストケースを更新し、新しい仕様を満たすことを確認するテストケースを作成する。

今回の変更点は以前述べたとおり速度情報の追加であるため、この点に絞ってテストケース作成を説明する。

 

準備

 

現時点の仕様確認コード準備

  • 起動時、テーブルが表示され、”行”,”保存”,”日時”,”緯度”,”経度”,”高度”列ラベルがこの順で表示される。
  • 起動時、テーブルの内容は空である。
  • trlファイルを開くと、テーブルに内容が表示される(空ではなくなる)。

上記のテストケースは以下のようになる。1つめのテストtestOpenでは1点目と2点目を、2つめのテストtestOpenFileでは3点目を確認している。実際のソースコードはこちら

// import static org.fest.assertions.Assertions.*;
// import static org.fest.swing.core.matcher.JButtonMatcher.withText;
// import static org.junit.Assert.*;

    @Test
    public void testOpen() {
        assertEquals(0, table.columnIndexFor("行"));
        assertEquals(1, table.columnIndexFor("保存"));
        assertEquals(2, table.columnIndexFor("日時"));
        assertEquals(3, table.columnIndexFor("緯度"));
        assertEquals(4, table.columnIndexFor("経度"));
        assertEquals(5, table.columnIndexFor("高度"));

        assertEquals("起動時のテーブルは空", 0, table.rowCount());
    }

    @Test
    public void testOpenFile() {
        final File file = new File("testdata/ver112.trl").getAbsoluteFile();

        window.button(withText("開く...")).click();
        JFileChooserFixture chooser = new JFileChooserFixture(robot);
        chooser.selectFile(file);
        chooser.approve();

        assertThat(table.rowCount()).as("ファイルの内容が表示される").isGreaterThan(0);
    }
}

 

新仕様対応確認テストケースの作成

こちらに記載したとおり、Holux M-241のファームウェアVer1.13では、速度情報が記録される。従って、更新後のプログラムの動作が次のようになればOKとする。

  • 起動時、テーブルが表示され、”行”,”保存”,”日時”,”緯度”,”経度”,”高度”,”速度”列ラベルがこの順で表示される。
  • 起動時、テーブルの内容は空である。
  • trlファイルを開くと、テーブルに内容が表示される(空ではなくなる)。
  • Ver1.12以前のファイルを開いた場合は、速度列は空欄とする。

太字部分が追加・更新した仕様である。

上記を満たせばOKとなるように、テストケースの差分は以下のようになる。ソースコードはこちら

// import static org.fest.swing.data.TableCell.row;

    @Test
    public void testOpen() {
...
        assertEquals(6, table.columnIndexFor("速度"));
...
    }

    @Test
    public void testOpenFile() {
...
        final int colSpeed = table.columnIndexFor("速度");
        table.cell(row(0).column(colSpeed)).requireValue("");
    }
}

これを実行すると、実プログラム修正を行っていない今の時点ではもちろん失敗する。

このような形でテストケースの作成し、テストケースに適合するように実プログラムを更新していく。

2009/10/04

NetBeansでFEST-Swingを利用する その3 JUnitテストケース作成

実際のプログラムを用いてFEST-Swingを利用していく。元となるソースコードは、2008年7月に作成したもの(TrlLogEditor_src.zip)を用いる。

準備

  • 上記TrlLogEditor_src.zipをダウンロード、展開し、NetBeansへプロジェクトとして追加する。。
  • 前回までに述べたNetBeansの設定を行う。

テストケースクラス作成

今回テスト対象とするクラスはyukihane.gps.gui.MainWindowである。そこで、プロジェクトウィンドウ上の、ソースパッケージ以下にあるMainWindow.javaを右クリックする。コンテクストメニューで ツール > JUnitテストを作成 を選択し、ウィザードを進める。JUnitのバージョンは4.xを選ぶ。

ウィザードを完了させると、テストパッケージ以下にMainWindowTest.javaが作成される。

初期処理、終了処理の追加

まず、Robotインスタンスを生成する。Robotは1テスト内では1つのインスタンスを使いまわす必要があるため、setUpメソッドで生成し、フィールドとして保持する。また、今回は、テストごとにメインウィンドウを起動させるようにする[1]ので、こちらもsetUpに含める。

public class MainWindowTest {

    private Robot robot;
    private FrameFixture window;

    @Before
    public void setUp() {
        robot = BasicRobot.robotWithNewAwtHierarchy();
        window = new FrameFixture(robot, new MainWindow());
        window.show();
    }
}

終了時にはcleanUpメソッドを呼ぶ必要がある[1, 2]。

    @After
    public void tearDown() {
        robot.cleanUp();
    }

ダミーのテストメソッドを作成し、うまく動くか試してみる。以下のメソッドを追加した後、Ctrl+F6でこのファイルを実行する(以前記載したとおり、JButtonMatcher.withTextをstatic importする)。

    @Test
    public void testOpen() {
        window.button(withText("開く...")).click();
    }

備考:実行時間について

上で作成したテストケースを実行すると、私の環境では2秒強かかる。これに対し、メソッド内の処理を全てコメントアウトした場合では0.1秒程度で、差は2秒ほどとなっている。これが意味するところは、"GUIの自動テストにはかなりの時間がかかる"ということである。今日のGUIアプリはスタンドアローンでは無くサーバとの通信機能を持っていることも多いと思うが、このテストのためにモックオブジェクトを利用しリフレクションを多用するなどしていると、更に実行時間は延びてくる。

また、実行時のマウスカーソルの動きを見れば分かるとおり、FEST-SwingはGUIイベントを擬似生成するのではなく、Robotを用い実際にボタンを押し、ユーザが画面操作を行うのと同じ仕組みでイベントを発生させる。従って、テスト実行中は端末で他の作業が行えない

これらを踏まえると、規模がそれなりに大きいものに適用する際には、テスト専用の端末を用意しておくか、夜間にAntで自動実行するような仕組みを検討しておくべきだと考える。

参考



2009/10/03

FEST-Swing1.2a3の新機能で気付いたこと

今まで1.1を使用しており、今回初めて1.2a3を利用してみているのだが、コンポーネントのtextでコンポーネントを探すことができるようになっているようだ。

従来より、FEST-Swingはコンポーネントをnameにより識別する方針を採っている。

例えば”適用”ボタンがあったとすると、このボタンのnameは”適用”ではなく、別途設定する必要がある。ちなみに、このときの”適用”は何かというとtextプロパティである。

確かに国際化を考えると、”適用”でコンポーネントを識別することには問題がある(例えば英語化したときは”Apply”ボタンになるだろう)が、過去作成したプログラムのテストを自動化したいという理由だけではソースコードの修正が行えない場合もあり、また、そもそも全部のコンポーネントに今からnameを設定するのはコストが高い場合も多い(命名規則を作って、仕様書に1列追加して、...云々)。

1.1では、このような場合どうしたかというと、国際化する際のことは取り敢えず忘れて、こちらのエントリ後半に記載しているようにGenericTypeMatcherを使用して、textプロパティで識別を行っていた。

1.2a3では、この方法がライブラリで提供されるようになったようだ。JButtonMatcher.withText(String)を使用し、以下のような記述ができる。

// import static org.fest.swing.core.matcher.JButtonMatcher.withText;
window.button(withText("適用")).click();

レガシーコードに対しても、直感的なコーディングができるようになったのではないだろうか。

以下、余談で国際化について。

メニューから呼び出される昨日のテストを行う際、どのメニューアイテムを選択するか特定する必要があるが、この特定方法はmenuItemWithPathメソッドの引数に、メニューアイテム名を指定する必要があり、いずれにせよ国際化済みプログラムのテストが実現できなかったと当時は考えたのだが、本来はどうあるべきなのだろうか。

 

参考

2009/10/02

NetBeansでFEST-Swingを利用する その2 JavaDoc作成

わき道に逸れるが、FEST-SwingのパッケージにJavaDoc(webではここで参照できる)が含まれていないようなので、作成しておくことにする。

AntのJavadocタスクを作成しても良いが、ここではせっかくなのでNetBeansで作成してみる。

まず、作成対象のソースコードを展開する。展開はjarコマンドを用いてコマンドプロンプトから jar –xfv fest-swing-1.2a3-sources.jar 等のように実行するか、一旦拡張子を.jarから.zipに変更し、zipが解凍できるユーティリティを使用すれば良い。なお、WinRARのように.jarのまま解凍できるものもある。

次に、NetBeansで新規プロジェクトを作成する。このとき”既存のソースを利用するJavaプロジェクト”を選び、ソースパッケージフォルダには先ほど展開したソースディレクトリを含める。

fest05

含めるファイルは**,除外するファイルは**/META-INF/**としておけば良い。

fest06

プロジェクトが作成されたら、コンパイルが通るようにするため、文字コードの変更を行う。プロジェクトを右クリックし、プロパティーを選択、プロジェクトプロパティーダイアログを開く。その後、ソースを選択し、エンコーディングにISO-8859-4を指定する。

fest07

最後に、プロジェクトプロパティーダイアログを閉じ、プロジェクトを右クリック、”Javadocを生成”を選択して完了。

生成されたjavadocディレクトリを適切なディレクトリに移し、ライブラリマネージャでFEST-Swingのjavadocにそのディレクトリを指定する。

今回作成したプロジェクトや展開したソースコードはもう不要であるため削除してしまってよい。

NetBeansでFEST-Swingを利用する その1 インストール

一度FEST-Swingについて記載したことがあるが、もう少し実用的なエントリを書きたいと思う。ちょうど、GUIを持つアプリケーションを修正する必要が出たので、このアプリにおける、実際の利用方法を記述していく。

ここでは、NetBeans上でライブラリとして扱うための準備について記載する。

 

FEST-Swingの最新バージョンであるfest-swing-1.2a3.zipをダウンロードし、適切な場所に展開する。

NetBeans6.7.1を起動、メニューの ツール > ライブラリ を選択し、ライブラリマネージャを起動する。

fest01

ダイアログ左下のボタン”新規ライブラリ”を押し、新規ライブラリを作成する。ライブラリ名は”FEST-Swing”とでもしておき、ライブラリの種類は”クラスライブラリ”を選ぶ。

ライブラリマネージャダイアログ左部のツリービューで、作成したFEST-Swingを選択し、ダイアログ右部のクラスパスタブ内リストへ、先に展開したfest-swing-1.2a3.jar及び libディレクトリ内のdebug-1.0.jar, assert-1.1.jar, fest-reflect-1.1.jar, fest-fest-util-1.1.jar, jcip-annotations-1.0.jarの6ファイルを追加する。

fest02

同様に、ソースタブ内リストへ上記に対応するxxx-sources.jar 6ファイルを追加する。

fest03

了解”ボタンを押し、ライブラリマネージャダイアログを閉じる。

以上でNetBeansでFEST-Swingを利用できる設定が行えた。続いてプロジェクトでFEST-Swingを利用する設定を行う。

画面左上部のプロジェクトツリーで該当のプロジェクトを右クリックし、プロパティーを選択する。

表示されたプロジェクトプロパティーダイアログで、”ライブラリ”を選択し、”テストをコンパイル”タブ内のリストへ、先ほど作成したライブラリFEST-Swingを追加する。追加は”ライブラリを追加”ボタンから行う。

fest04

以上でこのプロジェクトからFEST-Swing APIが呼び出せるようになった。

2009/02/15

FESTを使用してJava GUI (Swing)のテストを実行してみる

FEST-Swingを使用してみた手順を記載する。なお、NetBeans用プラグインも存在するが、現時点ではリリースされているバージョンより古いものがインストールされる。ライブラリを自動で作成してくれる程度のことしか行っていないように見えるので、プラグインにこだわる必要は無いだろう。

環境設定

テストライブラリにfest-assert-1.0.jar, fest-reflect-1.0.jar, fest-swing-1.0.jar, fest-util-1.0.jarを追加する。(これらはプラグインに含まれていたものであり、実際に全て使用しているのかは未検証)

fest01

恒常的に使用するのであれば、ライブラリ化しておくと良いだろう。

 

1つのチェックボックスの状態をテストする

チェックボックスを用いてテストを行ってみる。今回はJUnit4.5を使用した。

以下がテスト用のウィンドウである。JFrameの中にチェックボックスコンポーネントを1個配置した(ソースコード)。

fest02

 

まず、テストケース実行前後に以下の処理を実行させる。

    @Before
    public void setUp() {
        window = new FrameFixture(new NewJFrame());
        window.show(); // shows the frame to test
    }

    @After
    public void tearDown() {
        window.cleanUp();
    }

続いて、チェックボックスが選択された状態になっていることを確認するテストケースを作成する(ソースコード)。

    @Test
    public void testCheckBox() {
        window.checkBox().requireSelected();
    }

ここまでのNetBeansプロジェクトとソースコードはこちら(NetBeansで実行するには、test_lib以下に冒頭のfestライブラリをコピーする必要がある)。

Shift+F6を押してテストケースを実行してみると、下記のエラーとなる(折り返されて見づらいが、実際には1行で表示される)。

junit.framework.AssertionFailedError: [javax.swing.JCheckBox[name=null, text='チェックボックス', selected=false, enabled=true, visible=true, showing=true] - property:'selected'] expected:<true> but was:<false>

selected(true)を期待していたが、実際にはfalseになっている、というJUnitの報告が挙がっている。

そこで、起動初期状態として、チェックボックスが選択された状態にするコードを追加する。

    public NewJFrame() {
        initComponents();
        jCheckBox1.setSelected(true); // 追加
    }

NewJFrame.javaに上記の3行目を追加し、Ctrl+F6でテストを実行する。結果、JUnitは成功して終了する。[補足:本来は、この程度であれば上記のように手でコードを追加するより、Mattise上のプロパティ(下図)で設定を行った方が、手段の統一としては良いのかもしれない]

fest03

2つのチェックボックスの状態をチェックする

先ほどJUnitが成功したJFrameに、チェックボックスをもう1つ追加する(プロジェクト, ソースコード)。

fest11

ここで、先ほど成功したはずのテストケースを再度実行すると、失敗してしまう。

Found more than one component using matcher org.fest.swing.core.TypeMatcher[type=javax.swing.JCheckBox, requireShowing=true].

チェックボックスを探したところ、複数のコンポーネントがヒットしたのでどちらか特定できなくなっている。

これに対するfest側が想定している通常の対応は、コンポーネントに名前を付けておき、その名前で識別しよう、というものだ。

メインのコードで

    public NewJFrame() {
        initComponents();
        jCheckBox1.setSelected(true); // 追加
        jCheckBox1.setName("1個目"); // コンポーネントに名前を設定
        jCheckBox2.setName("2nd");
    }

setNameメソッドで名前をセットしておけば、テストコード上ではその名前で識別できるようになる。

    @Test
    public void testCheckBox() {
        window.checkBox("1個目").requireSelected(); // 引数に名前を指定
        window.checkBox("2nd").requireNotSelected();
    }

ただ、あらかじめこのことを知っていれば良いのだが、通常、名前をつけるようなことはあるのだろうか。私は今回初めてnameという属性があるのを知ったのだが...

nameの設定を行っていない場合、表示されている文字列(setTextで設定した文字列)で2つのチェックボックスを識別できれば良いのでは、と考えた。

今まで使用してきたcheckboxメソッドは、Javadocを見るとContainerFixtureで実装されていることが分かる。また、このメソッドはnameを引数にとるものの他、GenericTypeMatcherを引数にとるものがある。今回はこちらを用いる(ソースコード)。

    @Test
    public void testCheckBox() {
        GenericTypeMatcher<JCheckBox> matcher1 =
                new GenericTypeMatcher<JCheckBox>(JCheckBox.class) {

                    @Override
                    protected boolean isMatching(JCheckBox component) {
                        return "チェックボックス".equals(component.getText()) ? true : false;
                    }
                };
        GenericTypeMatcher<JCheckBox> matcher2 =
                new GenericTypeMatcher<JCheckBox>(JCheckBox.class) {

                    @Override
                    protected boolean isMatching(JCheckBox component) {
                        return "2nd check".equals(component.getText()) ? true : false;
                    }
                };

                window.checkBox(matcher1).requireSelected();
                window.checkBox(matcher2).requireNotSelected();
    }

上記のように、isMatchingの中でコンポーネントを識別できるような実装を行うことになる。

コンポーネントごとにmatcherを作る必要があるようなので、かなり面倒な作業になる。festを使用するのであればnameは設定しておくべき、ということなのだろう。

なお、表示文字列で識別する場合、多言語対応しているアプリケーションでは問題が出るであろうことが予想される。(自分で実装しない部分、たとえばOK/キャンセルダイアログのボタン等も実行環境によって文字列が自動で切り替わる。このようなものについても検討が必要かもしれないが、未検証。)

 

参考:

other sites

  • follow us in feedly
  • github
  • stackoverflow

ソフトウェアエンジニアとして影響を受けた書籍

  • Christain Bauer: HIBERNATE イン アクション

    Christain Bauer: HIBERNATE イン アクション
    理論と実践が双方とも素晴らしい製品であるHibernate。本書はそのプロダクトを書名に冠していますが、Hibernateを使うつもりがなく、ORマッピングの解説書として読むにしても十分な良書です。Second EditionとしてJava Persistence With Hibernateという書籍も出版されていますが、残念ながら現在のところ 和訳はされていません。-インアクションは2.xの、Java Persistence-は3.1の頃のものなので、最新版とはちょっと違うところもあることに注意。 (★★★★★)

  • アンドリュー・S・タネンバウム: 分散システム 原理とパラダイム 第2版

    アンドリュー・S・タネンバウム: 分散システム 原理とパラダイム 第2版
    クライアント/サーバシステムを構築する上で必要となる知識が総論されてます。Web技術者も、フレームワーク部分を開発するのであれば必読。 (★★★★★)

  • Joel Spolsky∥著: ジョエル・オン・ソフトウェア

    Joel Spolsky∥著: ジョエル・オン・ソフトウェア
    前述の書籍「ソフトウエア開発プロフェッショナル」をより砕いたもの、という感じでしょうか。 前書きではプログラマでなくSE向けの本のように書かれているが、プログラマが読んでも面白い本であると思われます。 SEになった新人(あるいはそういう会社に入る/入りたての人)にとっては、これからどういったことが仕事を遂行していく上で起こりえるのか、どのように考えて行なっていけばいいのか決定する助けになると思います。 元は″Joel on Software″というブログの記事で、web上でも一部日本語で読めます。 http://japanese.joelonsoftware.com/ (★★★)

  • ドナルド・C・ゴース,ジェラルド・M・ワインバーグ: ライト、ついてますか

    ドナルド・C・ゴース,ジェラルド・M・ワインバーグ: ライト、ついてますか
    問題解決(一昔前のの流行語で言うところの『ソリューション』)能力は、システムエンジニアのスキルとして備えるべきもののうちのひとつです。しかし、これは難しい。学校で出されるテストと違い、唯一の、(問題提出者が想定している)解を求めるだけが「問題解決」では無いからです。そもそも、何が問題なのか、それは本当に問題なのか、それは本当に解決すべき問題なのか、その問題解決方法は正しいのか、などを解決しなければ、「その解は正しいのか」に辿りつくことができません。この本の最も良いところのひとつは、本があまり厚くないこと。すぐに読めるし、何回も読み返す気になるでしょう。 (★★★★★)

  • スティーブ・マコネル: ソフトウエア開発プロフェッショナル

    スティーブ・マコネル: ソフトウエア開発プロフェッショナル
    コードコンプリートで有名なスティーブマコネルの著書。新人SEに読んで欲しい。個人として業界の中でどうあるべきか、組織としてどうあるべきか、SEのプロ意識とは?SEの心構え概論、といったところでしょうか。また、業界における資格の重要性についても説かれています。この業界では資格が特に軽んじられる傾向がありますが、この傾向はどんな弊害をもたらすのか、将来的にこの業界は資格に対してどのような姿勢で臨んでいくべきなのか。日経BP社では(他の出版社もだが)最近、似たような類いのあまり面白くない書籍が乱出版されていますが、この本は別格だと思うので安心して購入して欲しいと思います。 (★★★★★)

無料ブログはココログ