カテゴリー「unittest」の13件の記事

2011/02/15

TestNGが使いづらい

今までJUnitを使ってきていたのですが、とあるプロダクトのTestCaseがTestNGに統合されていることもあり、TestNGにも手を出し始めています。

しかし、Ant TaskからのTestNG実行(with testng.xml)がすごく使い勝手が悪いですね…

具体的には、何かの拍子にTestCaseがコンパイルエラーにでもなって、実行時にClass Not Foundになると、そこで終了してしまうという。

こちらこちらを読むと、TestNGプロダクトのポリシーとしてそういうことになっているそうですが、夜間実行を仕掛けておいて、朝になってみてみると全然動いてなかった、とか嫌すぎるんですけども。

少し前のバージョンでは、実行途中で終わってしまうので質が悪かったですが、最新バージョン(TESTNG-426で対応されたのでしょうか)では、テストケースを実行する前に検査してくれるようになっていたので何とか…という感じです。

CIツールでもTestNG対応は普通に為されてるようなのですが、この問題にはどう対応しているんでしょうかね…?

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/26

easymockを利用してみる(2) – easymockのインストールとHelloWorld

Mock Objectツールは、interfaceを元にモックのインスタンスを生成するのが基本らしいが、easymockにせよjMockにせよ、classからモックインスタンス生成が可能になっている。今回はこの機能を利用する。

動作させるために、easymockのサイトにあるEasyMock2.5.2、Class Extension2.4、そしてcglibのサイトからcglib-nodep-2.2.jarの3点をクラスパスに設定すればよい。

 

次に、動作確認のため、dJUnitのサイトにあるHelloWorldをeasymockで動作させてみる。

   1: import java.io.PrintStream;
   2: import org.junit.After;
   3: import org.junit.Before;
   4: import org.junit.Test;
   5: import static org.easymock.classextension.EasyMock.*;
   6:  
   7: public class HelloWorldTest {
   8:  
   9:     private PrintStream mock;
  10:     private PrintStream original;
  11:  
  12:     @Before
  13:     public void setUp() {
  14:         mock = createMock(PrintStream.class);
  15:         original = System.out;
  16:         System.setOut(mock);
  17:     }
  18:  
  19:     @After
  20:     public void tearDown() {
  21:         System.setOut(original);
  22:     }
  23:  
  24:     @Test
  25:     public void testMain() {
  26:         mock.println("Hello World.");
  27:         replay(mock);
  28:         HelloWorld.main(null);
  29:         verify(mock);
  30:     }
  31: }

 

dJUnitではPrintStreamクラス自体に作用できるので、「ソース例」の2番目のコード10行目

String arg = (String) getArgument("java.io.PrintStream", "println", 0);

でPrintStream#println(String)の引数に設定された文字列を取得できている。

一方、easymockでは、createMockメソッドで作成したモックオブジェクト(mock)を、System.setOutメソッドで標準出力に設定してやる必要がある(上コード10-13行目)。元々のSystem.outは、普段と何ら変わらないオブジェクトなので、動作を設定することは出来ないためだ。

その他補足すると、djUnitでjunit4相当の記述をしようと思うと、始めにMockObjectManager.initializeメソッドを呼ぶ必要があり[参考]、Virtual Mock Objectもいきなり利用できる(=いきなりクラスを差し替えられる)わけではない。

 

…とここまで書いて気付いたのだが、easymockでは、private methodやstatic methodに対する操作が行えない。上記のSystem.outのようなstatic fieldもそうだ(上の例ではたまたまsetterがあるので問題ないが)。

これらのmethod,fieldに対して操作しようと思うと、easymock(等)を拡張するPowerMockJMockitやJMockitのページで参照されているいくつかのプロダクトがあるようだが、結局dJUnitとアプローチは変わらない様に見える。そうするとやはり実行速度がネックになりそうだ。

PowerMockはまさにdJUnitと同じような仕組みのようだ。JMockitのサイトのPowerMockについて、以下の記述がある。

PowerMock uses custom class loaders (usually one per test class) in order to generate modified versions of the mocked classes. Such heavy use of custom class loaders can lead to conflicts with third-party libraries, hence the need to sometimes use the @PowerMockIgnore("package.to.be.ignored") annotation on test classes.

ちなみにdJUnitでは、無視するパッケージはAntのxml、djunitタスクのプロパティに設定する(excludesPath)。

JMockitはjavaagentを利用しているようだが、やはり動作速度は遅いらしい。

ssogabe – twitter:

JMockitをフルに使ったら、テスト時間が2sから7.2sに増加した。予想以上に遅い。

当初の目的である、クライアント/サーバ間の通信でサーバのモックを作成するという点についてはeasymockで問題ないと思うが、上記の点はどうしたものか。

2009/11/25

easymockを利用してみる(1) – Virtual Mock Object と Mock Object の違い

以前Mock Objectに少し触れたことがあるが、私自身使用したことが無かったので今回試してみた。

また、Virtual Mock Object として、dJUnitを利用したことがあるので、これとの比較も書いてみたいと思う(したがって、タイトルのVirtual Mock Objectは、正確にはdJUnitを指すものであって、Virtual Mock Object全体の性質であるとは限らない)。

 

テストカバレッジについて日本語のサイトを検索すると、dJUnitがよくヒットする。また、Eclipseプラグインとしても提供されており、導入難易度が低いせいか実プロジェクトでもよく利用されているようだが、実際に利用してみて何点か不満が出た。例を以下に記載する。

  • カバレッジについて、かなり古いバージョンのJCoverageを利用している。
    • 現在オープンソース版のCloverが提供されなくなって久しいが、dJUnitでは当時のGPL版のものを用いているはず。
  • Eclipse以外から利用するのに、設定が煩雑である。特にVirtual Mock Objectを利用する場合。
  • Antで実行する場合、専用のタスク(junitタスクに似てはいるが)で実行する必要がある。
  • テスト対象の規模が大きくなると、実用に耐えられないくらい遅い。
    • 1テストケースごとに、クラスロードに数分、というようなことも。
      • forkは常にtrueなので、テストケースごとにVMが分かれる。
      • 仕様なのかバグなのか不明だが、テストケースをロードした直後(具体的には@BeforeClassアノテーションを付けたメソッド内)にしかVMO動作設定できない現象が出たので、テストを細分化してVM起動からやり直す必要が出た。
  • dJUnit、Hibernate共にasm(cglib)を利用するのだが、必要とされるバージョンが異なるのではまる。
    • これについては、Hibernateが古いバージョンを利用しているのが問題なのだが。
    • easymockやjMockでもcglibを利用しているようなので、この問題はdJUnitに限ったことではないのかもしれない。

Mock Objectはオブジェクトに対して動作を設定するのに対し、Virtual Mock Objectはクラス自体の動作を書き換えるらしい。このためVirtual Mock Objectは動作速度が遅くなりがちなのではないかと思う。また、Mock Objectではこの問題が表れにくいのではないかと期待しているが、冒頭に記載したとおりMock Objectの使用経験が無いため実際のところは未知である。

JCoverageの問題については、Coberturaを利用すればよいだろう。

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メソッドの引数に、メニューアイテム名を指定する必要があり、いずれにせよ国際化済みプログラムのテストが実現できなかったと当時は考えたのだが、本来はどうあるべきなのだろうか。

 

参考

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社では(他の出版社もだが)最近、似たような類いのあまり面白くない書籍が乱出版されていますが、この本は別格だと思うので安心して購入して欲しいと思います。 (★★★★★)

無料ブログはココログ