ラベル Jetty の投稿を表示しています。 すべての投稿を表示
ラベル Jetty の投稿を表示しています。 すべての投稿を表示

2012年3月9日金曜日

JavaFX + JUnit で javascriptのunit testできるようにしてやるんで、これからハマっていってやんよ - 2

どーも、季節性鬱症がかなりひどくてやる気が0なミケです。


ツイッターでJavaFXの同期がむずいとつぶやいていたら、@skrbさんから



こんなツイートをいただきました。

JavaFXとJUnitのThreadについて丁寧に解説されていて、動作できたようです。

というわけで、コピペプログラマーとしてはコピペしないわけにはいきません。

早速Jettyも使ってunit testできるようにしましょう。


@BeforeClassで起動するWebサービス


JUnitのテストコードの中でServerを持っているのが大分辛くなってきたので、

Serverのコードは外に出すことにしました。

まだ、試作段階なので、Handlerクラスなども固定でしか動きません。




一応、サーバーのアドレスとかポートも設定できるようにしておきたいので、

コンストラクターで指定できるようにはしてあります。

ただし、現段階では使っていません。


TestBrowser


JavaFXのWebEngineを使ってテストコードを走らせるクラスです。

@skrbさんのブログの内容をそのままコピーしています。

なお、一部分を変更しています。

どうやら@Testメソッドを実行時に、画面のロードが完了していないなどの同期の部分で

失敗したため、javascript上でロードしたことをマークするようにして、ロードが完了するまでは待機するように

改造してあります。




なかなか、ソースが読みづらくてごめんなさい。試作段階ということで許しておくれ。

Threadの同期と非同期は難しいですね。

@skrbさんには非常に感謝しております。



実際のテストコード


実際のテストコードです。

これは単純に数値を計算するfunctionを呼び出しているだけです。




なお、@BeforeにてApplication#launch(Class<T extends Application>, java.lang.String[])

第二引数でサーバーのURLを渡してあります。

これは、アプリケーション側でgetParameter()メソッドを使うと取得することができます。

サーバーのポートなどの問題でURLを変更することはよくあるので、

この辺は可変にしておきたかったです。



実行結果


実行結果は次のような感じになります。



お気づきな方もいると思いますが、doStringTestの方は@Ignoreしています。

これ、@Ignoreを外すと、2つ目のテストが今は動かない状態なんです。



これはTestBrowserの部分の作りがまだまだ粗いため、2つ目のテストが実行できない状態になっているからですね。

TestBrowserの起動を@BeforeClassに移動して、

@Before毎にページを読み直すような仕様に変更しようと考えています。


とりあえず、今はこんな感じです。


どうやら…


@skrbさんの周囲で、

JRubyのなひさんJenkinsの川口さんといった

錚々たる面々のお方から反応があったらしく、

意外と面白そうなプロダクトになりそうな気がしてきました。


Seleniumと比べて


SeleniumなどのUIテスト系と比べて、僕がこのFxJsJUnitでやりたかったのはjavascriptのunit testなので、

実はUIはあまり気にしていないんです。

なので、縦幅がいくつとかはあまり興味がなくて、

純粋にjavascriptの関数に対してTDDを実施していけるような状態にしたいというのが第一の目標です。

あと@skrbさんのブログにあったとおり、

GUIは別に表示しなくてもいいという点からいくと、

Seleniumのように画面がポコポコ生まれなくていいというのが強みかなと思っていたりします。

あとはwebkit搭載なので、巷に最近溢れているブラウザー(Chrome/Android用のブラウザ/Safari)に対応できるので、

モバイル系のアプリにも対応できるテストツールになりうるかもしれません。


いずれにせよ、もう少し安定して動かせるようにしたいところです。


なお、コードはgist( https://gist.github.com/2001562 )上にあげていますので、ぜひご意見等いただければ幸いです。

あと、そのうちちゃんとしたプロジェクトとしてgithubにプロジェクトを作りたいと思っています。







2012年3月6日火曜日

JavaFX + JUnit で javascriptのunit testできるようにしてやるんで、これからハマっていってやんよ - 1

こんちわ。

大分暖かくなって来ました。

暖かくなってくると、旅に出たくなります。

特に、春なら鹿がお勧めです。

ついでに勉強会にも参加しましょう。

というわけで、


鹿駆動勉強会



とかいうのにエントリーすることになりました。

能楽堂でLTをするらしいです。


で、ネタ


JavaFX1のときはあまり惹かれなかったんですが、

JavaFX2になって、Webkit搭載となったので、

おお、JUnitでjavascriptのテストできんじゃね~と思ったので、

試してみることにしました。

まあ、その辺の経緯はtogetterにまとめてます。


事前にJavaFXとThreadにハマった言い訳を書いておく


もともと業務SEだったオレなので、

JavaのGUIとかはあまり触ったことがないというか、

ほとんど触ったことがないというか、

Javaを勉強した時にチョコっと触っただけです。はい。

なので、かなりクソいコードになっています。

サーバーサイドのJavaは書いたことありますです。



テスト対象のJavascript




特に大したことのないコードです。

数字を返す関数、文字列を加工して返す関数、オブジェクトを返す関数です。



テスト戦略???


テスト対象のJavascriptは大したことのないものですが、

ajaxアプリケーションのテストもやっていきたいので、

サーバーを立てていこうと思います。

JUnitでテストサーバーを立てる場合、

以前のエントリにも書きましたが、

Jettyエンベデッドサーバーを用いていきます。

サーバーが立ち上がった後で、ブラウザを立ち上げて、javascriptのテストをしていくという流れになります。

したがって、書いていくJUnitのコードは次のようになります。

  • @BeforeClassにてJettyサーバーを立ち上げる。
  • @Beforeにてブラウザーを立ち上げる。
  • @TestにてJavascriptのテストを実行する。
  • @Afterにてブラウザーを終了させる。
  • @AfterClassにてJettyサーバーを終了する。


JUnitのコード


次のような感じです。




なお、ハンドラーをJUnitに書くと若干読みづらくなるので、ハンドラーは別クラスにしてあります。





ハマる様子をとくと見よ


さて、JsJUnitを実行してみるとこんな感じになる。





JavaFXのWebViewはJavaFXのThreadで立ち上げなければならないらしいだって(´・ω・`)



さて、まだまだハマるのだが、それは後日。

2011年12月4日日曜日

herokuにJavaアプリをデプロイ…のはずが、mavenの勉強で終わってしまった件

herokuがJavaに対応ということで、試してみました。

結果ですが、

Mavenが全然わからないレベルなので、herokuにJavaアプリをデプロイするところで凄い時間がかかってしまった…


Herokuのガイド通りにアプリを作成して、デプロイしたところ…


[INFO] ------------------------------------------------------------------------
       [INFO] BUILD FAILURE
       [INFO] ------------------------------------------------------------------------
       [INFO] Total time: 6.396s
       [INFO] Finished at: Sun Dec 04 11:26:33 UTC 2011
       [INFO] Final Memory: 9M/490M
       [INFO] ------------------------------------------------------------------------
       [ERROR] Failed to execute goal org.codehaus.mojo:appassembler-maven-plugin:1.1:assemble (default) on project orz.mikeneck.heroku.first: A type incompatibility occured while executing org.codehaus.mojo:appassembler-maven-plugin:1.1:assemble: java.lang.String cannot be cast to org.codehaus.mojo.appassembler.Program
       [ERROR] -----------------------------------------------------
       [ERROR] realm =    plugin>org.codehaus.mojo:appassembler-maven-plugin:1.1
       [ERROR] strategy = org.codehaus.plexus.classworlds.strategy.SelfFirstStrategy
       [ERROR] urls[0] = file:/app/tmp/repo.git/.cache/.m2/repository/org/codehaus/mojo/appassembler-maven-plugin/1.1/appassembler-maven-plugin-1.1.jar
       [ERROR] urls[1] = file:/app/tmp/repo.git/.cache/.m2/repository/org/codehaus/mojo/appassembler/appassembler-model/1.1/appassembler-model-1.1.jar
       [ERROR] urls[2] = file:/app/tmp/repo.git/.cache/.m2/repository/net/java/dev/stax-utils/stax-utils/20060502/stax-utils-20060502.jar
       [ERROR] urls[3] = file:/app/tmp/repo.git/.cache/.m2/repository/stax/stax/1.1.1-dev/stax-1.1.1-dev.jar
       [ERROR] urls[4] = file:/app/tmp/repo.git/.cache/.m2/repository/org/codehaus/plexus/plexus-utils/1.5.6/plexus-utils-1.5.6.jar
       [ERROR] urls[5] = file:/app/tmp/repo.git/.cache/.m2/repository/stax/stax-api/1.0.1/stax-api-1.0.1.jar
       [ERROR] Number of foreign imports: 1
       [ERROR] import: Entry[import  from realm ClassRealm[maven.api, parent: null]]
       [ERROR] 
       [ERROR] -----------------------------------------------------
       [ERROR] -> [Help 1]
       [ERROR] 
       [ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
       [ERROR] Re-run Maven using the -X switch to enable full debug logging.
       [ERROR] 
       [ERROR] For more information about the errors and possible solutions, please read the following articles:
       [ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/PluginExecutionException
 !     Failed to build app with Maven
 !     Heroku push rejected, failed to compile Java app


おそらくmavenをちゃんと知っている人なら、たやすく回避できるんだろうな。

で、ググっていたら同じ問題を抱えていた人のブログを発見。

herokuのガイドのmavenの記述に誤りがあるらしいです。



<build>
        <plugins>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>appassembler-maven-plugin</artifactId>
                <version>1.1.1</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals><goal>assemble</goal></goals>
                        <configuration>
                            <assembleDirectory>target</assembleDirectory>
                            <generateRepository>false</generateRepository>
                            <programs>
                                <program>
                                    <mainClass>HelloWorld</mainClass>
                                    <name>webapp</name>
                                </program>
                            </programs>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>





<build>
        <plugins>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>appassembler-maven-plugin</artifactId>
                <version>1.1.1</version>
                <configuration>
                    <assembleDirectory>target</assembleDirectory>
                    <programs>
                        <program>
                            <mainClass>orz.mikeneck.heroku.first.HerokuServer</mainClass>
                            <name>webapp</name>
                        </program>
                    </programs>
                </configuration>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>assemble</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                </configuration>
            </plugin>
        </plugins>
    </build>


まとめると、こんな感じです。

  • mavenではコンパイラーの指定がデフォルトで1.3になっているので、1.6に変更
  • appassembler-maven-pluginの記述が微妙に間違っている





2011年4月17日日曜日

JUnitでWebサービスのモックを作る

ソーシャル系のアプリやほかのWebサービスと連動するアプリの単体テストをする場合、
対向するWebサービスをどうしても利用しないとテストできないようなケースが発生する。

たとえば、Twitterアプリを作成している場合など。

Twitterのようなすでに動いているサービスが対向するwebサービスであれば、
テスト用のアカウントを作成しておいて、
そこにテスト用のTweetを入れておけばよいのではあるが、
しかしエンタープライズな場合だと、
そのような準備をできないこともある。

SIerの場合、後の工程(つまり総合試験)にそれらのテストを持って行って、
人月投入するというパターンがあるが、それはそれで、非常にまずい。

そんなわけで、手軽にWebサーバーを構築したいのだが、
tomcatにサーバーをまた立てて、モックのアプリサービスを書いて、
云々というのも非常に面倒臭い。

そこで登場するのがJettyという軽量Webサーバーです。
これはJUnitでもサーバーを立ち上げられる優れもので、テスト用にも利用出来るなかなか優れたういやつです。
ちなみに、現在はバージョン8まで登場しているそうですが、本稿で使用するのは7.3.1です。



さて、早速コードをと言いたいところですが、
今回のサンプルは先に仕様の説明から。
  • Webサーバーはlocalhostにポート3000で立てます。
  • どのようなリクエストが来ても、レスポンスコード200 : OKを返します。
  • コンテンツタイプは"application/json"を返します。
  • メッセージ部分は{"test_result" : "ok"}というjsonが設定されます。

では、早速JUnitのテストコード@BeforeClass@AfterClassからどうぞ。


public class HttpGetResourceTest {

    private static final String TEST_RESULT = "{\"test_result\" : \"ok\"}";

    private static final int PORT_NUMBER = 3000;

    private static final String LOCAL_HOST = "localhost";

    private static Server server;

    /**
     * サーバーを起動する。
     */
    @BeforeClass
    public static void setUpBeforeClass() throws Exception {
        InetSocketAddress inet = new InetSocketAddress(LOCAL_HOST, PORT_NUMBER);
        server = new Server(inet);
        
        // 後述するHandlerの実装。
        Handler handler = new RequestHandlerImpl();
        server.setHandler(handler);
        
        server.start();
    }

    /**
     * サーバーを終了する。
     */
    @AfterClass
    public static void tearDownAfterClass() throws Exception {
        server.stop();
        server.destroy();
    }



@BeforeClassではサーバーを起動して、@AfterClassはサーバーをシャットダウンします。

一応このテストクラスが実行されている間は、サーバーはローカルホスト3000番で起動します。

実際にどのような処理がなされるかは、このテストの内部クラスRequestHandlerImplに記述します。

では、RequestHandlerImplをどうぞ。


    private static class RequestHandlerImpl extends AbstractHandler {

        @Override
        public void handle(String target, Request baseRequest,
                HttpServletRequest request, HttpServletResponse response)
                throws IOException, ServletException {
            
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json");
            response.setStatus(HttpServletResponse.SC_OK);
            
            Writer writer = response.getWriter();
            writer.write(TEST_RESULT);
            
            HttpConnection connection =
                HttpConnection.getCurrentConnection();
            Request req = connection.getRequest();
            req.setHandled(true);
        }
    }


ここでは本当に先程の仕様通りに
  • どのようなリクエストが来ても、レスポンスコード200 : OKを返します。
  • コンテンツタイプは"application/json"を返します。
  • メッセージ部分は{"test_result" : "ok"}というjsonが設定されます。

となっています。
あまり説明いらないですね。

では、サンプルのテストコードを書いてみましょう。
なお、このサンプルではApache HttpComponentsを利用しています。


    @Test
    public void testIsServerReady() throws Exception {
        URI uri = URIUtils.createURI("http",
                LOCAL_HOST, PORT_NUMBER,
                "/search",
                "user=mike_neck&start=10",
                null);
        HttpGet hGet = new HttpGet(uri);
        HttpClient client = new DefaultHttpClient();
        HttpResponse res = client.execute(hGet);
        
        HttpEntity entity = res.getEntity();
        
        if(entity != null){
            StringBuilder builder = null;
            InputStream input = null;
            try {
                input = entity.getContent();
                InputStreamReader reader =
                        new InputStreamReader(input, "UTF-8");
                builder = new StringBuilder();
                while(reader.ready())
                    builder.append((char)reader.read());
            }finally{
                input.close();
            }
            assertThat("testIsServerReady",
                builder.toString(),
                is(TEST_RESULT));
        }else{
            fail();
        }
    }


適当なクエリーをつけてGetで呼び出しました。
ここの部分は実際のクラスを入れておく部分ですね。



と、まあこんな感じなわけですが、
多少依存性がありますので、その情報も書いておきます。

必要なライブラリーは
commons-logging.jar
jetty-continuation.jar
jetty-http.jar
jetty-io.jar
jetty-server.jar
jetty-util.jar
servlet-api.jar

です。

皆さん、Web連携アプリのテストもしっかりとやりましょう。