2012/11/16 1:57 am
ant fix
Uncategorized, ,

build.xml を書くのがどうも苦手だ。

たとえば trunk/frontend/ の下の 10 個程度の javascript ソースを minify して、指定のディレクトリに置きたい。そういうタスクはないので、自分で書くか、既存の minifier を exec することになる。

とりあえず簡単なほう、つまり既存の minifier を呼び出す方で行ってみるのだけど、1 つの js ソースだけならともかく複数ある場合はどうしたら? 仮に jsminify みたいなタスクがあって、それが子要素として fileset を受け付けるならば、こんなイメージになるはず:

<jsminify base="trunk" todir="output">
  <fileset includes="frontend/**/*.js" />
</jsminify>

しかしそんなタスクはない。なので、直接 minifer を呼ぶのではなく、適当な php スクリプトを呼び出すようにして、その中でディレクトリを読み、見つけた js ソースを片っ端から minifier にかけるようにしている。

こんな調子で ant だけではできなさそうな箇所は全部 php スクリプトを挟んでいるのが今の状況なのだった。いやまあ php はいっぱしのグルー言語なのだから、そういう使い方は間違ってないといえば間違ってないと思うけど。なんかとても中途半端だ。

と思ったらこういうのがあるんだね。ant 上で foreach 的なことができるのか。あらやだ素敵!

2012/08/24 11:33 pm
brushing up the tests #3: ChromeDriver instantiation
Uncategorized, , , ,

ということでテストを書き始める。正確に言うとテストそのものは jsUnit でやってたものを変換するだけなので、土台を書く。

chrome へのオプションを指定しつつ ChromeDriver を生成するのに、

import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.remote.DesiredCapabilities;
  :
DesiredCapabilities cap = DesiredCapabilities.chrome();
cap.setCapabikity("chrome.switches", Arrays.asList("--load-extension=/path/to/extension"));
driver = new ChromeDriver(cap);

と書いたのだが、ChromeDriver を上記のようなコンストラクタで生成するのは deprecated だよ、と怒られる。
これは

import org.openqa.selenium.chrome.ChromeDriverService;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.chrome.ChromeDriver;
  :
ChromeDriverService service = ChromeDriverService.createDefaultService();
ChromeOptions options = new ChromeOptions();
options.addArguments("--load-extension=/path/to/extension");
driver = new ChromeDriver(service, options);

と書くのがナウいらしい。
参考: https://groups.google.com/forum/?fromgroups=#!topic/selenium-users/-shBLr8WTq0
ありがとうありがとう。

2012/08/23 5:10 am
brushing up the tests #2
Uncategorized, , , ,

とりあえず単に Firefox を起動させることはできたので、次に Opera を起動させてみる。

まず classpath に OperaDriver を追加する:

<path id="classpath.test">
    <!-- operadriver -->
    <pathelement location="wasavi/wd-tests/libs/operadriver-v0.17/operadriver-v0.17.jar" />
</path>

テストファイルで OperaDriver を参照するために import を追加する:

import com.opera.core.systems.OperaDriver;

とりあえずこれだけでいいはず……と起動してみたら、Opera が起動しない。wiki を見ると現在の OperaDriver は、opera.exe の場所を割と決めうちの場所からしか探さないようだ。うちの Opera は C:\Program Files\Opera\current\opera.exe にいるのであった。

これは前述の wiki に書いてあるとおり、DesiredCapabilities オブジェクトに設定を突っ込めば上書きできるので、そうした。これでとりあえず Opera 自体は起動するようになったが、自動的にリモートデバッガが開いて何かをしようとしているところで接続できない云々というエラーを吐いて終了してしまう。どういうことなの。

ここはどうも勘違いしていたようだ。先の記事で Selenium Server は今回はいらないと書いたが、必要のようだ。Firefox で試したら普通に起動したので惑わされた。selenium-server-standalone-2.25.0.jar をダウンロードして、あらかじめ

$ java -jar selenium-server-standalone-2.25.0.jar

などとしてサーバを起動させておくおく必要がある。ただ、せっかく ant で一本化しているのだからサーバの起動と終了も自動的に行わせたい。そこで
http://wiki.openqa.org/display/SRC/Selenium-RC+and+Continuous+Integration
を参考に build.xml にいろいろ追加した。これで Opera も起動した。

ということで Opera でのテスト準備も整った。次は Chrome で、ChromeDriver は
https://code.google.com/p/chromedriver/downloads/list
からダウンロードしてくるわけなのだが。zip が揃っていてとりあえず最新の持ってくればそれに jar が入ってるのかな? と期待したらなんと zip の中身は chromedriver.exe ファイル 1 つなのであった。うん……!? 何かのインストーラなの?

と思いつつ実行してみるとコマンドプロンプト的ウィンドウが開きサーバ的動作を始めたようなログが出てきた。はあ、Chrome 場合はこれをサーバとして使うのね。Chrome にまつわるエトセトラは
http://code.google.com/p/selenium/wiki/ChromeDriver
にまとまっているが、それによると ChromeDriver は子プロセスでサーバを起動するモード、すでに起動してあるサーバと http でやりとりするモード、そして chrome 専用のサーバを起動させるモードと 3 形態の使い方があるようだ。chromedriver.exe は %PATH% が通っている場所に置くか、webdriver.chrome.driver システムプロパティにパスを書いておく必要がある。

とりあえず chromedriver.exe を使う方法で行ってみる。

というわけで、なんとか 3 ブラウザを起動させるところには達した。build.xml の該当部分はこうなった:

<!--
    test with selenium
    ================================================================================
-->

<path id="classpath.test">
    <!-- junit -->
    <pathelement location="wasavi/wd-tests/libs/junit/junit-4.10.jar" />

    <!-- operadriver -->
    <pathelement location="wasavi/wd-tests/libs/operadriver-v0.17/operadriver-v0.17.jar" />

    <!-- selenium core -->
    <pathelement location="wasavi/wd-tests/libs/selenium-2.25.0/selenium-java-2.25.0.jar" />

    <!-- libs for which selenium depends -->
    <fileset dir="wasavi/wd-tests/libs/selenium-2.25.0/libs">
        <include name="*.jar" />
        <exclude name="operadriver-*.jar" />
    </fileset>

    <!-- all tests -->
    <pathelement location="wasavi/wd-tests/dst" />
</path>

<target name="runtest_start_server">
    <java jar="wasavi/wd-tests/libs/selenium-server-standalone-2.25.0.jar"
        fork="true" spawn="true">
    </java>
</target>

<target name="runtest_stop_server">
    <get taskname="selenium-shutdown"
        src="http://localhost:4444/selenium-server/driver/?cmd=shutDownSeleniumServer"
        dest="stop-server-result.txt" ignoreerrors="true" />
    <echo message="standalone-server stopped." />
</target>

<target name="runtest_compile">
    <javac srcdir="wasavi/wd-tests/src" destdir="wasavi/wd-tests/dst" includeantruntime="false">
        <classpath refid="classpath.test"/>
    </javac>
</target>

<target name="runtest_junit" depends="runtest_compile">
    <junit haltonfailure="false">
        <classpath refid="classpath.test" />
        <sysproperty
            key="webdriver.chrome.driver"
            value="wasavi/wd-tests/libs/chromedriver.exe" />
        <formatter type="brief" usefile="false" />
        <test name="WasaviTestSuite" />
    </junit>
</target>

<target name="runtest">
    <parallel>
        <antcall target="runtest_start_server" />
        <sequential>
            <echo message="running standslone-server..." />
            <waitfor maxwait="2" maxwaitunit="minute" checkevery="100">
                <http url="http://localhost:4444/selenium-server/driver/?cmd=testComplete" />
            </waitfor>
            <antcall target="runtest_junit" />
            <antcall target="runtest_stop_server" />
        </sequential>
    </parallel>
</target>

あとはテストの骨組みに注力することになる。

2012/08/22 5:26 pm
brushing up the tests
Uncategorized, , , ,

wasavi の自動テストは、jsUnit で行っているが、いくつか問題が無いわけではない。過去の記事のとおり、クリップボードを参照する場合など、wasavi の ex コマンドは条件によっては非同期で行われる。しかし jsUnit のテストの仕組みでは非同期的に実行される機能をテストするのは難しい。いやそもそもそのレベルになると “unit” test の域を超えて機能テストになっているのでこれは別に jsUnit の問題ではない。

機能テストのレベルで自動テストするにはどうするか。その方面で有名なのは Selenium だ。これを使ってみることにしよう。

Selenium は java 製のアプリケーションで、ブラウザに対する操作を機械的にエミュレートすることでテストを行う。IE/Firefox/Safari/Chrome と主要なブラウザはサポートされている。そしてなんということか、Opera もサポートされている。現在、操作のエミュレーションの仕組みで区別された Selenium RC (Remote Control) と Selenium Webdriver の 2 種類があるが、前者は公式に deprecated であり、Webdriver 版が最新だそうなのでこちらを使うことにする。ちなみに Opera 向け Webdriver や、Ruby 版の自動テストツールである Watir への対応などは Opera 社自らが手がけているみたい。

さてテストは基本的には java で記述し、コンパイルし、実行するが、その API は他の言語へのバインディングも提供されている。しかし wasavi のビルドですでに ant を使っているので、素直に java で書くことにしよう。ant のタスクに junit そのものがあるので、単にそれを呼ぶだけで済む。

ゴール
Selenium を用いた自動的な機能テストを行えるようにする。テストは

$ ant runtest

のように ant のターゲットの 1 つにする。

必要なファイル
http://seleniumhq.org/download/ からダウンロードしてくる。さまざまなファイルがあるが、

  • Selenium IDE: Firefox で動くプラグインで、ブラウザの操作をインタラクティブに記録していろんな言語によるテストスクリプトにエクスポートできるとか。今回は関係ない
  • Selenium Server: テストを行うマシンを別に誂える場合にサーバ機能を担当するアプリケーション、だと思うけど今回は関係ない。ちょっと勘違いしていた。これも必要。
  • The Internet Explorer Driver Server: 字の通り IE でテストする際に必要なもの。32bit 版と 64bit 版があるので Selenium 本体とは別になっているのでしょう、たぶん。もちろん今回は関係ない
  • Selenium Client Drivers: ローカルマシンでテストする際に必要な Selenium の本体。今回必要なのはその java 版なのでそれを落とす
  • Third Party Browser Drivers: Opera 版や Chrome 版の Webdriver。Selenium 本体を落とせばこれらの個別の Webdriver を含んでいるようなので、たぶん落とす必要はないと思う。ただし各ドライバが個別にアップデートされることはありえるのでそのときは必要かも
  • その他のファイル: とりあえず今回は関係ない

また、JUnit の jar も必要なのでもらってくる。github にさまざまなファイルが置いてあるが、最新の snapshot はとりあえず避けて junit-4.10.jar を使うことにする。

ファイルの配置

+ wasavi
    + wd-tests
        + src
        + dst
        + libs
            + selenium-2.25.0
            + junit

こんな感じで配置。テストは src の下に置き、.class は dst の下に置く。

build.xml への追加

<!--
    test
    ================================================================================
-->

<path id="classpath.test">
    <!-- junit -->
    <pathelement location="wasavi/wd-tests/libs/junit/junit-4.10.jar" />

    <!-- selenium core -->
    <pathelement location="wasavi/wd-tests/libs/selenium-2.25.0/selenium-java-2.25.0.jar" />

    <!-- libs for which selenium depends -->
    <fileset dir="wasavi/wd-tests/libs/selenium-2.25.0/libs">
        <include name="*.jar" />
    </fileset>

    <!-- all tests -->
    <pathelement location="wasavi/wd-tests/dst" />
</path>

<target name="runtest-compile">
    <javac srcdir="wasavi/wd-tests/src" destdir="wasavi/wd-tests/dst" includeantruntime="false">
        <classpath refid="classpath.test"/>
    </javac>
</target>

<target name="runtest" depends="runtest-compile">
    <junit haltonfailure="false">
        <classpath refid="classpath.test" />
        <formatter type="brief" usefile="false" />
        <test name="WasaviTestSuite" />
    </junit>
</target>

こんな感じで追加。とりあえず独立したターゲットにしたが、ビルドプロセスの間に行うようにしても良いだろう。

テストを書く

import org.junit.*;
import static org.junit.Assert.*;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver;

public class WasaviTestSuite {
    private static WebDriver driver;

    @BeforeClass
    public static void beforeClass () {
        driver = new FirefoxDriver();
    }

    @AfterClass
    public static void afterClass () {
        if (driver != null) {
            driver.quit();
            driver = null;
        }
    }

    @Test
    public void foobar () {
        assertTrue("TestExample", true);
    }
}

とりあえず Firefox を起動させてみる。あと、実際にはテストは複数ファイルに分かれるのでテストスイートにしないといけない。なんか junit4 になったらいろいろ変わりすぎてて困る。

とりあえずここまで。

アーカイブ