ものがたり(旧)

atsushieno.hatenablog.com に続く

Google Native Driverの.NET portを作った (or: Androidのテストの仕組み)

表題のブツそのものに興味があって、それ以外はどうでもいいという人は、適当に後ろの方にあるリンクを拾って見てもらいたい。

一昨日、うちのタスクリストのようなものを眺めていて、Mono for Androidにもテストの仕組みが必要だろうと思って、半ば興味本位でAndroidのテストの仕組みを探っていた。Mono for Androidには、まだandroid.testのバインディング以外にユニットテストフレームワークが存在しない(し、これはJUnitのテストのannotationsをマッピングできない関係で、利用できるとは言い難い)。NUnitは動くかもしれないけど、それだけでは足りない。以下少しずつ説明していく。

Androidのテストの仕組みはどうなっているのか

developer.android.comにあるTesting Fundamentalsにも、大まかな話は載っているので、それを理解している人には必要ないかもしれないけど。

Androidの開発は、多くの人はEclipse ADTを使って行っていると思うし、Androidユニットテストを書いている人は多くがJUnitを使っていると思う(想像)。アプリケーションの中にテスト用に android.test.ActivityInstrumentationTestCase2 から派生したクラスを追加して、ADTの機能を使って "Run As" (あるいは "Debug As" )で "Android JUnit Test" を実行したりしていると思う。

これってどういう仕組みで実現しているんだろう?

この疑問にピンと来ない人もいるかもしれないので、もう少し長く書こう。Androidのクラスライブラリ android.jar の中には、android.test というJUnitベースのテストフレームワークがあって、皆それをテストケースに使っていることになる。そのテストコードが動作するのは、Android実機あるいはエミュレーター(以下これらは target と書くことにする)の上でということになるけど、そこにはJVMは存在しない。存在するのはDalvikであり、実行されているのは、いったんJavaバイトコードから変換されたDalvikのバイトコードだ。でも Android JUnit Test を実行している host は、Eclipseの中で動作するJava環境だ。どうやって別々のVMで動作しているテストの結果を共有しているんだろうか?

この疑問を解くキーポイントとして、JUnitの仕組みを知っておく必要があるかもしれない。僕はJUnitを一度も使ったことがない(!)ので、NUnitからの想像で書いてみる。JUnitにはTestRunnerというクラスがあって、これは渡されたテストを「実行する」存在として抽象化されている。通常は、渡されたテストケースのテストメソッドを適宜実行していくだけで足りるのだけど、もし必要があれば、ここに何らかの処理を挟んだり、そもそも何も実行しなかったりということすらできる。

Eclipse ADTとRemoteAndroidTestRunner

ADTは、このTestRunnerの抽象性を活用している。実のところ、Eclipse JDTに、今回行っているような「リモート環境で動作するテスト」を実行するためのorg.eclipse.jdt.internal.junit.runner.RemoteTestRunnerというクラスが存在している。ADTでは com.android.ide.eclipse.adt.internal.launch.junit.runtime.RemoteAdtTestRunner という長ったらしい名前のクラスがあって、これがリモートにあるTestRunnerとやりとりをしていることになる。RemoteAdtTestRunnerは、内部的にはddmlibというsdk用のライブラリに含まれるcom.android.ddmlib.testrunner.RemoteAndroidTestRunnerというクラスを利用している。

RemoteAndroidTestRunnerは、そんなに難しいことをしているわけではない。AndroidにはadbというUSB debuggingなどで使われるコンソールツールがあり、これでも利用できるshellという機能を利用している。shellで "am instrument ..." と命令を送ることで、target側でJUnitを実行して結果を送ってもらうことができるわけだ(instrumentationで送ることが出来るのはクラス名やテスト名であって、テストのコードそのものを送っているわけではない)。

バイス側ではandroid.app.Instrumentationを利用して実装されたandroid.test.InstrumentationTestRunnerが動作して、adb経由で実行を指示されたテストのクラスを名前から探し出して、それを実行している、と思う(target側はほとんど処理フローを追っていないので、この辺は想像だけど、まあ外れてはいないと思う)。

ちなみにAndroidのテストフレームワークにrobotiumというのがあるけど、これを取り込んでCIなど統合的なテストフレームワークを構築しているsciroccoでもこのRemoteAdtTestRunnerを取り込んでいるようだ

また、Android CTSも参考になるはずだと教えてもらったので、ソースを追ってみたのだけど、確かにここにもCTSで独自にリモートテストを実行するためのHostUnitTestRunnerという独自のTestRunnerが用意されている。これが独自に発展している理由はよく分からないけど、内部的には hosttestlibのcom.android.hosttest.DeviceTest を利用していて、これがddmlibを使っていて、結局は同じことを行っているようだ。

Native Driver for Androidの仕組み

さて、以上のようなことを調べて、Eclipse/ADTからtargetのテストがどのように実行できるかを、必要な範囲で把握することが出来た。それで、世にあるAndroid用テストフレームワークはどうなっているんだろうと思って、robotium、robolectric、native driverの3つを眺めてみた。robolectricはandroid.jarの機能の呼び出しをinjectして、あくまでeclipseのローカル側だけでコードを実行しているものだったので、これは僕らがMono for Androidで使うような性質のものではない。

というわけで、robotiumとnative driverが残ったのだけど、robotiumはinstrumentationの仕組みを使用した、target側だけのライブラリなので、RemoteAdtTestRunnerのような仕組みを用意してやらなければならない。一方、native driverは、その実装のコアでもあるselenium WebDriverの仕組みを利用して、host側のクライアントコードと、target側で動作しているサーバコード(中でjettyが動作している!)の命令をやり取りしていて、サーバは内部でinstrumentationの仕組みを利用して、外部からアプリケーションを操作する機能(というか権限)を実現している。

そして、native driverのクライアントのソースを見てみると、コードは実はそれほど多くなく、native driverのクライアントは、seleniumのWebDriver(これもGoogle製)の上に乗っかって、命令を少し拡張したり先のadb shellの機能を呼び出すように実装されたもので、その他の部分はWebDriver上で行われていた。seleniumのWebDriverには、実は.NET版の実装がある。これをベースにC#版のクライアントをJavaからの移植で実装すれば、Native DriverのC#版が作れるのではないかと気が付いた。

というわけで、思い立って移植してみたら、selenium WebDriverにも多少手を加える必要があったものの、割と簡単に実現できた。移植コードは1000行程度しかない。
http://dl.dropbox.com/u/493047/2011/09/nativedriver.net-0.1.tar.bz2

動かしてみたものを画質の悪いカメラで録画してみた例はこちら (or WebM)

使い方

Native Driverは、オリジナルでも実のところ多少ややこしい操作をしないと使えない。adb shellで、Native Driver本家のGettingStartedAndroidにある am instrument と forward を行う必要がある。そして、アプリケーションにserver-standalone.jarを組み込んで、 AndroidManifest.xml にinstrumentation要素を追加する必要がある。(以上は先のアーカイブの中でもREADMEに書いておいた。)

多分これだけやっておけば、後は他のアプリケーションでも使えるようになるはずだと思うのだけど、実のところまだnative driverのサンプルでしか動作確認出来ていない。もちろんMono for Androidで組み込んだものも動かしてみたいと思っている。(今動かそうとしているのだけど、まだハマり中)

総括

というわけで、今回はAndroidのテストがどのように実装されているかを追っかけて、それで得られた知識をもとに、Native Driverを移植してみた。割と簡単に動いてくれた。

たぶんseleniumクライアントがあるrubypythonでも、同じことが同じくらいの作業量で実現できると思う(わたしはseleniumをいじったのもnative driverをいじったのも初めてだったので、予備知識があったわけではない)。

Mono for Android 1.2

http://android.xamarin.com/Releases/Mono_for_Android_1/Release_1.2.0

わたしが開発に参加するようになって初めてのリリースになります。とはいえ、わたしが担当している部分はまだ入っておらず。近日中に次の開発版が出る予定なので、その時に数週間前に書いたネタを合わせて出そうと思います。

今回の大きな修正はGCまわりなどの安定化でしょうか。VS Addinのadbログ表示は割と便利になっています。機能的な追加や大掛かりな変更は次の開発版でいくつか入るので、今回は安定化リリースと見て良いと思います。

実のところ、次のバージョンアップが終わったらようやくマージ出来るような、さらに先の機能ばかり実装しているので、早く次のバージョンが正式版にならないかなあと待っている状態です。

WP7でOgg Vorbisを再生

id:atsushieno:20090325:p1 のOggMediaStreamSourceをWP7に対応させたものを作りました:
https://github.com/mono/mooncodecs

(csvorbis/moonvorbis.sln を開くとWP7用のライブラリプロジェクトとサンプルが入っているはずです。WP7以外のプロジェクトはmoonlight用で今回ノータッチなのでVSでは開けないかも。)

webからURLをもとにogg vorbisファイルをダウンロードして再生するだけのサンプルアプリケーションもXapDeployするだけで試せるように置いてあります:
http://dl.dropbox.com/u/493047/2011/09/Wp7VorbisTest.xap

この辺のAPISilverlightとWP7で共通なので、実のところほとんど修正は加えられていません。

昨日貸してもらった実機で動かしてみたので、再生できることまでは確認してあります。

id:atsushieno:20110512:p1 のmonodroidベースのAudioTrackの時は間にいろいろ無駄があったせいか、パフォーマンス的に難があったのですが、今回の実装は少なくとも何秒か聞いてみた限りではモタることもなく再生できていました。

MediaStreamSourceはMediaElement向けのクラスなので、XNAのSoundEffectなどに使うには別の実装が必要になりますが、いずれにしてもcsogg/csvorbisがそのまま動くので、難しくはないと思います。

MonoDevelop 2.6 released

Xamarinになって初めてのリリースになります。もう一昨日くらいの話ですが。デザイナー休暇中でロゴが2.4なのはご愛敬
http://monodevelop.com/Download/Release_Notes/Release_Notes_for_MonoDevelop_2.6

MonoDevelopはプレビュー版がずっとベータ版チャネルで公開されいたので、正式版と言っても今更感がありますが、とりあえずハイライトはNGit対応でしょうか。これはSharpenというdb4o由来のツールを使って、JGitを機械的JavaからC#に変換して、ソースを自動生成しているようです。JGitが新しくなったら、これを使ってNGitも自動的にバージョンアップする予定だとか。

.NET 4.0対応とかも、まあずっとプレビュー版があったので今更感がありますが、正式版しか知らなかったという人には朗報かもしれませんね。

以前のバージョンでフルスクラッチで書きなおされたエディタも動作は安定しています。ここ数カ月、エディタで落ちたことは一度もないです。

MD2.6はMonoTouchやMono for Androidでも使っていくlong term supported versionになりそうな感じです。まあ2.8のプレビュー版もほどなく出るとは思いますが。

よく考えてみたらわたし以前は翻訳いじってたんですよね。今回は(も)ほとんどいじってないです。差分は英語でお楽しみください…

Java 7でandroid apkをビルドできない問題の解決方法

小ネタ。kernel.orgクラック以来落ちっぱなしのandroid.git.kernel.orgが復旧したら書こうと思っていたのだけど、全然戻ってこないので、ソースへのリンク無しで書いちゃえ。

Java 7 SDKでビルドした.classファイルは、そのままではAndroidのDalvikのclasses.dex(apkに含まれているdalvikのバイトコードを全部まとめたやつ)に変換することができない。これはdxの com.android.dx.dex.cf.CfTranslator#translate() に渡されている com.android.dx.dex.cf.CfOptions のstrictNameCheckフィールドがデフォルトでtrueになっていて、com.android.dx.cf.direct.DirectClassFile が入力classファイルのバージョンをチェックして、51 (0033h) を拒絶するようにしているためだ。

だから、Java 7環境で生成したクラスファイルを対象にdxを実行すると、以下のようなメッセージが出てきて、そのクラスはclasses.dexに含まれないことになる:


trouble processing:
bad class file magic (cafebabe) or version (0033.0000)
...while parsing foo/bar/Baz.class
...while processing foo/bar/Baz.class

これを回避するには、コマンドラインツールのdxであれば、--no-strictオプションを渡してやると良い。その他の方法で直接CfTranslatorを呼び出しているのであれば、上記のstrictNameCheckをfalseにして処理すると良い。

ただしJava 7のバイトコードをdxが従来通りに処理してくれるかどうかは分からない(そのためにバージョンチェックがあって、それをoffにして使っているということは心に留めておくべき)。

初めてのWindows Phone 7プログラミング

と言っていいのかよくわかりませんが。

何やらandroid方面の人たちがWP7の開発を勉強する集まりをやるというので、ついうっかり見てしまったのですが、どう見ても普段.NETやってる人がいません。というわけで遊びに行ってきました。アウェイ感ありありです。冗談はさておき、そんな集まりならゼロから勉強だろうと思って行ってみたら、すでにけっこういじっている人もいたようで、僕の知らんAPIの話がさかんに流れていました。Silverlightと同じだから分かるだろうとたかをくくっていたのですが、よく考えたらSystem.Windows.Controlsの類は全然いじってなかった(ry

まあそんなわけで、初めてWP7を真面目にいじるということで、今回は昔作ったprocessingのSilverlight版をWP7で動かしてみました。

↑のサンプルのコードは、Visualizing Dataという書籍に含まれるサンプルを移植したもので、C#のプロジェクトは↓にあります。が、多分まだ誰もtsukimiをビルド出来る状態になっていないと思うので、まあ参考程度に…
http://dl.dropbox.com/u/493047/2011/08/Processing.WindowsPhone.Samples2.zip

ビジュアライジング・データ ―Processingによる情報視覚化手法

ビジュアライジング・データ ―Processingによる情報視覚化手法

移植ですが、割とサクッと動きました。tsukimi自体はApplicationクラスから派生したアプリケーションクラスからxapまで自前で生成してしまう程度にSilverlightに特化しているのですが、新規プロジェクトにprocessingから変換されたC#のソースを直接取り込んで、csprojの設定でスタートアップオブジェクトを生成されたクラスに設定するだけで、先のスクリーンショット程度のものは動きます。

tsukimi自体全然開発していなくて出来ないことだらけということはおいといて、これだけ簡単に移植できるなら、コントロールライブラリの類は移植しやすいかもしれないと思いました。アプリケーションのUIそのものは画面サイズ違いすぎて無理ってことが多そうですが(特にSilverlightのアプリは画面が無駄にでかいボンクラサイトも多いですし)。

WP7の勉強会はまたやるらしいので興味のある人は参加してみると良いと思います。(僕は作るものを思いついたら…)

mono 2.10.3

http://mono-project.com/Release_Notes_Mono_2.10.3

めでたくXamarinから初のリリースでOSX Lionにも対応した新バージョン…なのですが、ちと例外処理まわりででかいregressionがありそうなので、ちょっと修正を待った方が良いかもしれません。

ちなみにmonodevelop on .NET (Windows)でも起動時に固まる問題があって、これは別途修正待ちです。