afterburner.fxで始めるJavaFX

afterburner.fxとは

afterburner.fx は、CoCをベースとし、さらにDIを持つJavaFX用のMVP(Model-View-Presenter) frameworkです。

GitHubはこちらです。-> AdamBien / afterburner.fx

(2014-02-15 追記)

テンプレート作成後に、fxmlのファイル名を修正する作業を追加しました。

対応策としてfxmlのファイル名を全て小文字にする事で、解決しました。

step0. プロジェクトの作り方

プロジェクトのテンプレートは、pledbrook / lazybones を利用して作ると簡単です。

lazybonesは、GVM でインストールすることが出来ます。

$ gvm install lazybones

lazybonesがインストール出来たら、以下のコマンドを実行すると、lazybonesで利用できるテンプレート一覧が表示されます。

$ lazybones list
Available templates in pledbrook/lazybones-templates:

    gaelyk
    ratpack
    groovy-lib
    ratpack-lite
    java-basic
    dropwizard
    groovy-app
    afterburnerfx
    lazybones-project
    spring-boot-actuator

では、早速、afterburner.fxのプロジェクトを作ります。

$ lazybones create afterburnerfx ShibuyaJava05
Creating project from template afterburnerfx (latest) in 'ShibuyaJava05'
Cleaning up unclosed ZipFile for archive /Users/grimrose/.lazybones/templates/afterburnerfx-1.0.0.zip
Define value for 'group' [org.example]: org.grimrose        # グループ
Define value for 'version' [0.1.0-SNAPSHOT]: 0.1.0-SNAPSHOT # バージョン
Define value for 'packageName' [org.grimrose]: org.grimrose # パッケージ名
Define value for 'afterburnerVersion' [1.1]: 1.4.1          # afterburner.fxのバージョン

Afterburner.fx project template
-------------------------------

You've just created a basic [Afterburner.fx][afterburner.fx] application. It provides the
standard project structure and simple configurations for both [Gradle][gradle] and
[Maven][maven] to build and run the project.

The project's structure is laid out as follows

    <proj>
      |
      +- src
          |
          +- main
              |
              +- java
              |
                 // application sources
              |
              �+- resource
              |
                 // FXML and CSS

To compile and run the application with Gradle

    gradlew run

To compile and run the application with Maven

    mvn jfx:run

Additional information on specific settings for Gradle and Maven plugins can be found on
ther respective sites

 * [javafx-gradle][]
 * [javafx-maven][]

[gradle]: http://www.gradle.org
[maven]: http://maven.apache.org
[javafx-gradle]: https://bitbucket.org/shemnon/javafx-gradle
[javafx-maven]: http://zenjava.com/javafx/maven
[afterburner.fx]: http://afterburner.adam-bien.com


Project created in ShibuyaJava05!

これでプロジェクトの雛形が作られました。ファイル構成は、以下のように生成されます。

$ tree
.
├── README.md
├── build.gradle
├── gradle
│   ├── javafx.gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── pom.xml
└── src
    └── main
        ├── java
        │   └── org
        │       └── grimrose
        │           ├── ShibuyaJava05Main.java
        │           ├── ShibuyaJava05Presenter.java
        │           ├── ShibuyaJava05Service.java
        │           └── ShibuyaJava05View.java
        └── resources
            └── org
                └── grimrose
                    └── shibuyaJava05.fxml

10 directories, 14 files

では、このままアプリケーションを立ち上げてみます。

$ gradle run

こんな感じで起動したでしょうか。

../../../_images/step1.png

では、一旦終了して、次にIntelliJ IDEAのプロジェクトへ変換して取り込んでみましょう。

step1. IntelliJ IDEAで開発する準備

今回作成したプロジェクトは、grimrose / shibuya-java-05 にあります。

IntelliJ IDEAに取り込む前に、各種ライブラリのversionを上げておきます。

afterburner.fxは、javafx-gradle を利用しているので、最新の0.4.0へ更新します。

まず、gradle/javafx.gradle の以下の箇所を修正します。

//apply from: 'http://dl.bintray.com/content/shemnon/javafx-gradle/0.3.0/javafx.plugin'
apply from: 'http://dl.bintray.com/content/shemnon/javafx-gradle/0.4.0/javafx.plugin'

次に、Gradle Wrapperのversionを上げておきます。

build.gradle に以下のコードを追加します。

wrapper {
    gradleVersion = '1.10'
}

最後に、以下のコマンドを実行してプロジェクトへ変換します。

$ gradle wrapper
$ gradle idea

(2014-02-15 追記)

キャメルケースでプロジェクトを作成した場合、fxmlのファイル名も同様になってしまいます。

しかし、fxmlファイルを読み込む際に、全て小文字のパッケージ名 + ファイル名で読み込む 様になっているため、変更します。

$ mv src/main/resources/org/grimrose/shibuyaJava05.fxml src/main/resources/org/grimrose/shibuyajava05.fxml

step2. Viewを作る

今回は、以下のようなアプリケーションを作ってみます。

../../../_images/app.png

まずは、src/main/resources/org/grimrose/shibuyajava05.fxmlJavaFX Scene Builder で編集します。

IntelliJ IDEAであれば、ポップアップメニューの「Open Scene Builder」から起動できます。 起動しない場合、「Preferences」で「JavaFX」で検索したあと「Path to Scene Builder」にScene Builderの場所を登録します。

今回追加するのは、以下の3つです。

  • TableView
  • TableColumn(送信日時)
  • TableColumn(メッセージ)
../../../_images/table.png

step3. Modelを作る

今回は、Message(入力されたメッセージのオブジェクト)とMessageRow(メッセージを表示に利用するオブジェクト)をそれぞれ分けて作ってみます。

それぞれのオブジェクトを表示するために、src/main/java/org/grimrose/ShibuyaJava05Presenter.java を変更します。

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;

import javax.inject.Inject;
import java.text.DateFormat;
import java.util.Date;

public class ShibuyaJava05Presenter {

    public ObservableList<MessageRow> messageRowList = FXCollections.observableArrayList();

    @FXML
    TableView<MessageRow> table;

    @FXML
    TableColumn<MessageRow, String> submitAtColumn;

    @FXML
    TableColumn<MessageRow, String> messageColumn;

    @FXML
    void initialize() {
        table.setEditable(false);
        submitAtColumn.setCellValueFactory(new PropertyValueFactory<MessageRow, String>("submitAt"));
        messageColumn.setCellValueFactory(new PropertyValueFactory<MessageRow, String>("message"));

        Message message = new Message(new Date(), "ようこそ");

        String submitAt = DateFormat.getDateTimeInstance().format(message.getSubmitAt());
        MessageRow row = new MessageRow(submitAt, message.getMessage());

        messageRowList.add(row);

        table.setItems(messageRowList);
    }

        // ... 省略 ...
}

それでは、表示できるのか確認してみましょう。

../../../_images/view_model.png

step4. Serviceを作る

次に、PresenterがModelを呼ぶ際に使うServiceオブジェクトを作ってみます。

import java.text.DateFormat;

public class MessageRowService {

    public MessageRow create(Message message) {
        String submitAt = DateFormat.getDateTimeInstance().format(message.getSubmitAt());
        return new MessageRow(submitAt, message.getMessage());
    }

}
import java.util.Date;

public class MessageService {

    public Message create(String inputMessage) {
        return new Message(new Date(), inputMessage);
    }

}

PresenterがServiceを呼んで使えるようにします。ここでServiceに Injectアノテーション を付けることで、DIすることが出来ます。

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;

import javax.inject.Inject;

public class ShibuyaJava05Presenter {

    public ObservableList<MessageRow> messageRowList = FXCollections.observableArrayList();

    @FXML
    TableView<MessageRow> table;

    @FXML
    TableColumn<MessageRow, String> submitAtColumn;

    @FXML
    TableColumn<MessageRow, String> messageColumn;

    @Inject
    MessageRowService messageRowService;

    @Inject
    MessageService messageService;

    @FXML
    void initialize() {
        table.setEditable(false);
        submitAtColumn.setCellValueFactory(new PropertyValueFactory<MessageRow, String>("submitAt"));
        messageColumn.setCellValueFactory(new PropertyValueFactory<MessageRow, String>("message"));

        Message message = messageService.create("ようこそ");
        MessageRow row = messageRowService.create(message);

        messageRowList.add(row);

        table.setItems(messageRowList);
    }

    // ... 省略 ...
}

step5. Eventを編集する

入力したメッセージを画面に表示できるように変更します。

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;

import javax.inject.Inject;

public class ShibuyaJava05Presenter {

    // ... 省略 ...

    @FXML
    private TextField inputMessage;

    public void submitMessage(ActionEvent event) {
        ObservableList<MessageRow> currentList = table.getItems();

        Message message = messageService.create(inputMessage.getText());
        MessageRow row = messageRowService.create(message);

        currentList.add(row);
        inputMessage.setText("");
    }

}

Presenterの修正に合わせて、src/main/resources/org/grimrose/shibuyajava05.fxml も修正します。

その後起動して、メッセージを入力してボタンを押すと、こんな感じになります。

../../../_images/input_message.png

step6. 仕上げ

気づいた方もいらっしゃると思いますが、起動するとメッセージに以下のメッセージが表示されているかと思います。

Can't find bundle for base name org.grimrose.shibuyajava05, locale ja_JP

ResourceBundleを使って、ラベルの表示を変更します。

まず、以下の内容の src/main/resources/org/grimrose/shibuyajava05.properties を追加します。

title=Message
label=Input Message
submit=submit
datetime=submit at
messages=messages

IntelliJ IDEAであれば、 空の src/main/resources/org/grimrose/shibuyajava05_ja.properties を追加すると、ResourceBundleのタブが表示され、日本語対応のpropertiesファイルを編集できます。 詳しくは、そこは,やっぱりIntelliJ使っとこうよ をご覧ください。

src/main/resources/org/grimrose/shibuyajava05.fxml でベタ書きしていたラベルの箇所を編集して、利用していないServiceを削除して完了です。

最後に

Java7u51からセキュリティレベルが上がった為か、assembleタスクで生成されたjarから起動出来なくなったので、解決策を知っている方がいらっしゃればお教え下さい。

(2014-02-15 追記)

@aoetk さんから教えて頂きました。ありがとうございます。

原因となった箇所は、ここのようです。

セキュリティアップデートとは関係ありませんでした。申し訳ありません。

他にもJavaFXでDIをしたい場合、GuiceをJavaFXで使えるようにする cathive / fx-guice がありますので、こちらも参考にしてみてはいかがでしょうか。

JavaFXも作りやすい環境がだいぶ整って来ていますし、Event周りの無名クラスもJava8のlambdaによってだいぶ見やすくなると思いますので、試してみてはいかがでしょうか。

Vert.x Module Registryへの登録

まずはじめに

Vert.x Module Registrygrimrose / fluent-logger-vertx を公開したので、その方法を紹介したいと思います。

Vert.x からデータを Fluentd に流したかったので、 fluent / fluent-logger-java をラップしたModuleを作成しました。

Vert.x Moduleとは、Useful Vert.x components and modules のように出来る限り疎結合にし、繋げて使えるようにしてあるライブラリのことです。

Vert.x Moduleの作り方

Modules Manual を参考にModuleを作成しますが、再利用可能にするために mod.json へ必要事項を記載します。

mod.json についての詳細は Module descriptor file - mod.json を参照してください。

ここでは、公開時に必要になってくる項目を挙げます。

{
    "description": "Text description of what the module does",
    "licenses": ["JSON array of licenses used in the module"],
    "author": "Main author of the module (individual or organisation)"
}

以下の項目は、オプションですが、書いておくと検索に引っかかりやすくなると思います。

{
    "developers": ["JSON array of keywords that describe the module - these are used when searching."],
    "homepage": "URL of the project website",
    "keywords": ["JSON array of other developers of the module."],
}

完成した mod.json は以下のような内容です。

{

    "main": "org.grimrose.vertx.mods.FluentLoggerModule",
    "worker": true,

    "description": "Fluentd Logger Module for Vert.x",
    "licenses": ["The Apache Software License Version 2.0"],
    "author": "grimrose",

    "developers": ["grimrose"],
    "keywords": ["fluentd", "logger"],
    "homepage": "https://github.com/grimrose/fluent-logger-vertx",

    "auto-redeploy": true

}

Bintray でModuleを公開するためのzipファイルは、以下のタスクで生成します。

$ gradle clean modZip

ビルドが成功すると、以下の場所に生成されます。

$ ls build/libs/

Bintrayで公開

Vert.x Module RegistryHow do I get my module in Maven Central or bintray?Bintray への公開手順が載っています。

Bintray の概要については、GradleでBintrayにアップロードする手順 を参考にしてください。

流れとしては以下の通りです。

  1. vertx-mods という Repository を作成する。
  2. パッケージ名Package を作成する。
  3. リリースするバージョンVersion を作成する。
  4. リリースするバージョンFiles タブにて パッケージ名 のディレクトリを追加する。
  5. ディレクトリ配下に Vert.x Moduleの作り方 で作成した パッケージ名-version.zip ファイルをアップロードする。
  6. Publish を選択し、公開する。

公開が終わると以下のような構成になります。

https://bintray.com/grimrose/vertx-mods/fluent-logger-vertx/0.1.0/files

(2014-06-01 追記)

URLが変更されていました。

https://bintray.com/grimrose/vertx-mods/fluent-logger-vertx/0.1.0/view/files/fluent-logger-vertx

Vert.x Module Registryへ申請

Vert.x Module RegistryRegister your module にある registration form をクリックするとフォームが表示されるので、以下の項目を入力します。

  • Name
    • 今回は、Bintrayに公開してあるものを登録するので、 owner~module-name~version を入力します。
    • ですので、grimrose~fluent-logger-vertx~0.1.0 と入力します。
  • Location
    • 今回は、Bintrayに公開してあるものを登録するので、 Bintray を選択します。
  • 最後に Verify and submit for moderation のボタンを押すと申請出来ます。

Bintrayへの公開に失敗していたり、既に申請してある場合等、不備がある場合はエラーになります。

登録されると検索できるようになりますので、登録されるまでしばらく時間がかかるみたいなので待ちましょう。

Bintrayを使った公開手段を用意しているので、RubyやPython、JavaScriptといったMaven Centralに縁の無い人も使えるようになっているが嬉しいですね。

Vert.x Moduleのインストール

登録されれば、Installing modules manually from the repository を参考に、以下のコマンドでインストールすることが出来ます。

$ vertx install owner~module-name~version

また、Deploying a module programmatically を参考に、アプリケーションのVerticleに記載することで、自動的にデプロイされます。

container.deployModule("owner~module-name~version", config)

Let’s enjoy Vert.x life !

KotlinをVert.xで呼んでみた

この記事は、Kotlin Advent Calendar 2013 の23日目として書かれたものです。

22日目は @clomie さんの KAnnotatorでJavaライブラリでもNull-Safety機能を使う #Kotlin です。

24日目は、すから枠 Shinsuke-Abe さんの ことりんはスカラスキーの愛人になりうるか です。

きっかけ

Vert.x についての概要は 内部から見たVert.xとNode.jsとの比較 をご覧ください。

今回はPolyglotつまり多言語対応について注目してみます。

Vert.xの記事を探していたところ Kotlin in action: wrapping vert.x を見つけたので、動かしてみようと思います。

KotlinをVert.xで利用する

Vert.xのプロジェクトは vertx-gradle-template を元に作ります。

まず、 KotlinのGradleプラグイン を参考にGradleでKotlinを使えるようにします。

build.gradle に以下のコードを追加します。

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
    }
}

apply plugin: "kotlin"

ライブラリの依存関係は、以下のようにしました。 Kotlinのversionとgradle pluginのversionが一致している為、他のライブラリと同様に gradle.properties で管理します。

dependencies {

    compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"

    // twitterを利用するので
    compile 'org.twitter4j:twitter4j-core:3.0.5'

    // 日付変換に利用
    compile 'org.apache.commons:commons-lang3:3.1'

    // twitter4jのモックを作る為
    testCompile 'org.mockito:mockito-all:1.9.5'

    // 今回mainにGroovyを利用する為
    provided "org.codehaus.groovy:groovy-all:$groovyVersion"
    provided "io.vertx:lang-groovy:$groovyLangModVersion@jar"

}

test.testClassesDir = project.tasks.compileTestKotlin.destinationDir

次に、 src/main/kotlin のディレクトリを作成します。

次に、gradle idea と実行すれば、先ほど作成したkotlinディレクトリがソースディレクトリとして認識された状態のIntelliJ IDEAのプロジェクトが作られます。

今回のサンプルを作るに当たって、Kotlinを全く触ったことが無かったので、昨年のAdvent Calendarを参考にさせて頂きました。

24日目:ツイート検索アプリをつくる

その他に参考にさせて頂いたのは、以下のサイトです。

初めは、上記の記事を元に Verticle (Vert.xのモジュールを構成する最小の単位)を作って試行錯誤して見ましたが、 どうやら、Vert.xのversionが上がった為か上手く動かすことが出来ませんでした。

そこで、Kotlinで作られたクラスをGroovyのVerticleから呼び出すようにしました。

今回のサンプルは、こちらです。

https://github.com/grimrose/KotlinAdventCalendar2013

Twitter4J に渡すOAuthアクセストークンを conf.json に入力します。

{
    "OAuthConsumerKey": "Consumer key",

    "OAuthConsumerSecret": "Consumer secret",

    "OAuthAccessToken": "Access token",

    "OAuthAccessTokenSecret": "Access token secret"
}

gradle runMod を実行し、http://localhost:8080 で実行可能です。

感想

Kotlinを初めて触った感想としては、Javaと一緒に使おうとするといろいろと、Javaに引きずられている面があるなと感じました。 純粋にKotlinだけで書くと別の印象を受けると思います。

JavaからKotlinのコードを使う場合も、そんなに難しいとは思いませんでした。 ただ、まだ試した程度なので、本格的に使うとなると別の側面が出てくるかもしれません。

個人的にKotlinで面白いと思ったのは、 Function literals です。 Javaのクラスでは扱いづらいのも、この仕組みを利用することで使いやすくなるのではないかと思います。

Java8から導入される機能が、どんな影響をうけるか分かりませんが使いやすい言語になっていって欲しいです。

最後に

Providing Support for a New Language with Vert.x

Vert.xのPolyglotを支えているのは、このモジュール構成のおかげでもあります。

ですので、Kotlinもやろうと思えば、mod-lang-kotlinが作れてしまうわけです。

もし興味がある方は、挑戦してみてはいかがでしょうか。

Gradle Vagrant Pluginについて

この記事は、G*(Groovy, Grails ..) Advent Calendar 2013 の14日目として書かれたものです。

13日目は @kyon_mm さんの Groovyでデバッグするとき です。

15日目は @nobeans さんの Grails/Gradleの「さっきのテストレポート」をAlfredに表示してもらう です。

Gradle Vagrant Pluginとは

皆さんもちろん Vagrant 使って開発していらっしゃると思いますが、Gradle から使えるともっと便利なのになぁと思ったことはありませんか?

私はあります。

今回、そんなあなたに紹介するPluginは gradle-vagrant-plugin です。

はじめに

このpluginの出来る事は、CLIからVagrantを利用する際のコマンドをタスクとして実行できることです。

どうしても開発やテストの際に、複数のboxでやりたいけど、コマンドラインから入れるの面倒な場合ありますよね。

そういう時、タスクを動的に作れるGradleなら出来るんじゃないかとか思ったわけです。

多分、世界には、同じような事を思った人間が必ずいると思ったので検索したところ見つけました。

導入

  • Vagrantのインストール

まずはじめに、Vagrantをインストールしてください。

INSTALLING VAGRANT

http://docs.vagrantup.com/v2/installation/index.html

  • build.gradleについて

次に、build.gradle に以下のコードを追加します。

buildscript {
    repositories {
        jcenter()
    }

    dependencies {
        classpath 'org.gradle.api.plugins:gradle-vagrant-plugin:0.2'
    }
}

apply plugin: 'vagrant'

主なタスクはこちらです。 -> https://github.com/bmuschko/gradle-vagrant-plugin#tasks

  • Extensionについて

Extensionで必要な項目は、以下のように vagrant のClosureで書きます。

vagrant {
    boxDir = file('.')
}

boxDir は、必ず指定しなければならないみたいです。

Vagrantのコマンドを実行する場所なので、もし別のディレクトリで実行するのであれば、そちらを指定してください。

provider は、デフォルトは virtualbox が指定されていますので、別のproviderを使用する場合は、別のを指定してください。

  • タスクを追加する場合

org.gradle.api.plugins.vagrant.tasks.Vagrant がタスクのクラスになっているので、利用する場合はtypeに指定してください。

標準ではprovisionコマンドに対応したタスクが無いので追加してみましょう。

import org.gradle.api.plugins.vagrant.tasks.Vagrant

task vagrantProvision(type: Vagrant) {
    commands = ['provision']
}

commandsList<String> なので、気をつけてください。

最後に

GradleでVagrantを利用できるようになってから、他のタスクと組み合わせることが出来るようになったので、いろいろと自動化出来るようになったと思います。

今回のサンプルは、こちらです。 -> https://github.com/grimrose/vagrant-puppet-vertx-sample

Yokohama.groovy #20 で途中になっていたプロジェクトを利用してみました。

宣伝

現在、@shinyaa31 さんが主催されていた #yokohamagroovy を引き継いで、開催しております。

過去のイベントについてはこちらをご覧ください。 -> Yokohama.groovy

以下に該当する方で、興味がありましたら、よろしくお願いします。

  • Groovyを初めてみたいけど、どうしたらいいのか分からない。
  • Javaを使っているけど、もう少しカジュアルに使いたい。
  • GroovyでJavaのテスト書けるって聞いたから使ってみたい。

FluentLeniumの紹介について

この記事は、Java Advent Calendar 2013 の3日目として書かれたものです。

2日目は Mogami さんの Stream APIの始め方 です。

4日目は @khasunuma さんの 日本人のためのDate and Time API Tips です。

FluentLeniumとは

今回紹介するのは、SeleniumのWebDriverを流れるようなインターフェース(fluent interface)で実装できるようにしたテスティングフレームワークです。

FluentLenium / FluentLenium

assertionのライブラリとして、JUnit , FEST-Assert が利用できます。

はじめに

先日開催された システムテスト自動化カンファレンス2013 #stac2013 に参加してきました。

そこで行われた 実践で学ぶ、効率的な自動テストスクリプトのメンテナンス でSeleniumの基本的な使い方を学ぶことが出来ました。

独学で学んでいたため基礎となる部分を知ることが出来たのと、ハンズオンの内容を所属している会社の研修に取り入れてみたいと思ったため、とても参考になりました。

独学で学んでいた頃から思っていたのは、SeleniumのWebDriverのAPIが分かりにくく、使いづらいことでした。

もともとGroovyが好きなので Geb を個人的に使っていたのですが、会社ではGroovyを採用できる環境が無いため、勧めることが出来ませんでした。

そこで、もう少し使いやすくて、なんとかJavaで書けるフレームワークは無いものかと探していました。

FluentLeniumを知るきっかけは Play framework がこのFluentLeniumを採用しているということでした。

APIもSeleniumよりだいぶ使いやすくて、しかもJavaでも使えるので大変気に入りました。

今回は、ハンズオンの内容をFluentLeniumではどのように書けるのか紹介してみたいと思います。

導入

サンプルのレポジトリはこちらです。 grimrose / STARHandsOn

まず、 Gradle を利用して依存関係を解決します。

$ gradle init を実行して生成された build.gradle を修正します。

apply {
    plugin 'java'
    plugin 'idea'
    plugin 'eclipse'
}

repositories {
    mavenCentral()
}

dependencies {
    // AssertionのライブラリとしてFEST-Assertを利用
    testCompile 'org.fluentlenium:fluentlenium-festassert:0.9.1'

    // JUnitの場合、fluentlenium-coreを選択
    //testCompile 'org.fluentlenium:fluentlenium-core:0.9.1'
    testCompile 'junit:junit:4.11'
}

// testのディレクトリの配置が特殊なため
sourceSets {
    test {
        java {
            srcDir 'test'
        }
    }
}

test {
    // Gradleのtestタスク実行時に今回追加したファイルのみを対象とするように指定。
    include 'practicework_fluent/**'
}

利用するIDEに合わせて、 $ gradle idea または $ gradle eclipse を実行してプロジェクトを作成してください。

基本的な使い方

FluentLeniumは、 org.fluentlenium.adapter.FluentTest を継承してテストクラスを作成します。

デフォルトのWebDriverはFirefoxの為、ChromeのDriverを利用するには FluentTest#getDefaultDriver() をオーバーライドします。

FluentLeniumの特徴

Fluent#find() の引数に tag, id, class などのCSSセレクターを指定することで、対象となる要素を取得できます。

さらに Fluent#find() のショートカットとして、Fluent#$() が用意されているのでjQueryの様に記述する事が可能です。

パラメータを入力するには Fluent#fill() の引数にCSSセレクターを指定することで、対象となるinput要素に値をセットすることが出来ます。

その他に Fluent#click() などが用意されています。

WebDriverのAPIが使いたい場合は、FluentWebElement#getElement() で呼び出して使うことも出来ます。

// id:dayscountの内容を取得する。
find("#dayscount").getText();

// id:datePickの値を取得する。
$("#datePick").getValue();

// id:reserve_yearに"2013"を入力する場合
fill("#reserve_year").with("2013");

// id:reserve_termのプルダウンに”1”を入力する場合
fillSelect("#reserve_term").withValue("1");

// id:breakfast_onのラジオボタンをクリックする場合
click("#breakfast_on");
$("#breakfast_on").click();

// id:datePickで見つかった初めの要素にEnterKeyを入力する場合
$("#datePick").first().getElement().sendKeys(Keys.RETURN);

Page Object Pattern

FluentLeniumは、Page Object Patternを利用する為に org.fluentlenium.core.FluentPage を用意しています。

FluentPageは org.fluentlenium.core.Fluent を継承しているため、ほぼFluentTestと同じように利用できます。

ほぼ同じ事が出来てしまう為、Page Objectの責務を考慮して作成する必要があります。

また、FEST-Assertと組み合わせた場合、IDEの補完機能が優れている と、サクサクと書くことが出来ます。

見た目もJasmineやRSpecに似たような書き方が出来るので、何となく文章のように見えます。

// id:datefromの内容が"2013年12月8日"と一致するか
assertThat(confirmPage.dateFrom()).isEqualTo("2013年12月8日");
// id:guestname
assertThat(inputPage.$("#guestname").getValue()).isEmpty();

例えばテストを書き進めていく途中で、上記のような書き方をした後に、リファクタリングし、Page Objectへ移動するといったことが出来ます。

最後に

FluentLeniumは、Groovyと組み合わせるとさらにコード量を減らすことが出来ます。

test-groovy のディレクトリに幾つかGroovyを使って書いてみたものがありますので、参考になればと思います。

FluentLeniumの残念な点があるとすれば、WebDriverを複数切り替えたい場合、かなり大変だと言う点です。

JUnitのRule annotationと上手く組み合わせることが出来れば、もっと使いやすくなると思います。

先述のGebには そういった機能 があるので、もし複数のWebDriverを使いたくて、Groovyが採用できるのであれば、Gebをお勧めします。

また、FluentLeniumはcucumberとも連携できるみたいなので、興味のある方は是非試して公開してくださるとありがたいです。

以上、Seleniumでつらい思いをされた方は、ちょっとは楽になる FluentLenium を試してみてはいかがでしょうか?というお誘いでした。

追記

@garbagetown さんが Selenium Advent Calendar 2013 の四日目にFluentLeniumについて書いてくださいました。

FluentLenium http://garbagetown.hatenablog.com/entry/2013/12/04/115048

被ってしまい申し訳ありません…

併せてご覧ください。

watchdog-continuous-test-pluginの紹介

Watchdog Continuous Test Plugin https://bitbucket.org/grimrose/watchdog-continuous-test-plugin

きっかけ

Gradle を使ってRubyの Guard のようなにファイル監視をする方法は無いかと思い、Gradleプラグインを作ってみました。

自分自身guardを使ったことが無いので、同じようなことが出来ているのか分かりませんが、やりたかった 「ファイル監視 + testタスクの実行」 は実現出来たので公開してみました。

必須事項

java.nio.fileを利用しているので、Java7が必要になります。

使い方

まず、build.gradle にプラグインを使えるように buildscript を追加します。

buildscript {
    repositories {
        maven {
            url "http://oss.sonatype.org/content/repositories/snapshots/"
        }
    }
    dependencies {
        classpath group: 'org.bitbucket.grimrose', name: 'watchdog-continuous-test-plugin', version: '1.0-SNAPSHOT'
    }
}

apply plugin: 'watchdog-continuous-test'

次に監視の対象にするファイルやディレクトリと実行するタスクを指定します。

今回は、Javaプロジェクトのsrcディレクトリを対象に、testタスクを実行するように指定します。

watchdog {
    tasks = ['test']
    dirs = sourceSets*.allSource*.srcDirs.flatten()
}

準備が出来たので、実行してみましょう。

実行するタスクは watching です。

$ gradle watching

gradle wrapperの場合は、こちら

$ gradlew watching

これで、ファイルを追加されたり、変更があった場合にtestタスクが実行されます。

中断する場合は、Ctrl + C で終了してください。

最後に

今回のプラグインを作成するにあたって、参考にさせて頂いたサイトは以下の通りです。

maven centralへの公開方法についていろいろアドバイスしてくださった @mike_neck さん、ありがとうございました。

code-blockの確認

試しにHelloSpockのコードを張ってみる。

/**
 * name.size() == lengthが強調される。
 */
class HelloSpock extends spock.lang.Specification {

    def "length of Spock's and his friends' names"() {
        expect:
        name.size() == length

        where:
        name     | length
        "Spock"  | 5
        "Kirk"   | 4
        "Scotty" | 6
    }

}

second post

日本語で書けるかな?

first post

Hello Tinkerer!