sbt android plugin でテストできなかった件

はじめに

sbt android plugin とは、ScalaAndroid アプリを書くための素敵な方法だと聞き、特に考えもなく、どちらも経験が浅いのに、手を出しました。
最初のアプリは大した問題もなく、スムーズにリリースできました。しかし、機能追加に伴い、テストを書きたい、となったときに、この android plugin がサポートしている tests サブプロジェクトがうまく動きませんでした。

再現

https://github.com/jberkel/android-plugin/wiki/getting-started を参考に、プロジェクトを作成します。再現のためにすべてデフォルトを選択しました。

$ brew install giter8
$ g8 jberkel/android-app

Template for Android apps in Scala 

package [my.android.project]: 
name [My Android Project]: 
main_activity [MainActivity]: 
scala_version [2.9.1]: 
api_level [10]: 
useProguard [true]: 

Applied jberkel/android-app.g8 in my-android-project

プロジェクトディレクトリに移動して、sbt を起動し、パッケージを作ります。sbt は HomeBrew などでインストールしておいてください。

$ cd my-android-project
$ export ANDROID_HOME=/Applications/android-sdk-macosx
$ sbt

>android:package-debug

エミュレータを起動します。

> android:emulator-start my_avd
> android:start-emulator

問題なくサンプルプロジェクトが起動します。

テストプロジェクトの存在を知る

giter8 で作られたサンプルプロジェクトには、「tests」というサブプロジェクトが含まれています。https://github.com/jberkel/android-plugin/wiki/Building-Android-Test-Projects を参考に実行してみましょう。

> android:package-debug
> android:install-emulator
> tests/android:package-debug
> tests/android:install-emulator
> tests/android:test-emulator

このようになります。

[info] 
[info] my.android.project.tests.ActivityTests:INSTRUMENTATION_RESULT: shortMsg=java.lang.IllegalAccessError
[info] INSTRUMENTATION_RESULT: longMsg=java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation
[info] INSTRUMENTATION_CODE: 0
[success] Total time: 3 s, completed 2012/04/11 16:59:14

何が success だ、と言いたくなります。
ddms でログなどを見た結果、JavaScala もにわかな私の認識では、TypedResource などのクラスの解決がうまくいっていない、といったところでした。

打開

5日ほど試行錯誤したのですが、何とかテストを実行させられた手順が以下になります。

まず android plugin に手をいれるので github から落としてくる。

$ cd ..
$ git clone git://github.com/jberkel/android-plugin.git
$ cd android-plugin

AndroidInstall.scala を編集(対象コミットは 6f6e4b9)

--- a/src/main/scala/AndroidInstall.scala
+++ b/src/main/scala/AndroidInstall.scala
@@ -69,8 +69,7 @@ object AndroidInstall {
      classesMinJarPath, libraryJarPath, manifestPackage, proguardOption) =>
       if (useProguard) {
           val optimizationOptions = if (proguardOptimizations.isEmpty) Seq("-dontoptimize") else proguardOptimizations
-          val manifestr = List("!META-INF/MANIFEST.MF", "R.class", "R$*.class",
-                               "TR.class", "TR$.class", "library.properties")
+          val manifestr = List("!META-INF/MANIFEST.MF", "library.properties")
           val sep = JFile.pathSeparator
           val inJars = ("\"" + classDirectory.absolutePath + "\"") +:
                        proguardInJars.map("\""+_+"\""+manifestr.mkString("(", ",!**/", ")"))

72行目あたり、manifestr から "R.class", "R$*.class", "TR.class", "TR$.class" を削除します。このへんは ProGuard という機構の設定をしているようです。

編集したら、publish-local します。

$ sbt publish-local

サンプルプロジェクトに戻り、project/plugins.sbt を編集します。

--- a/project/plugins.sbt
+++ b/project/plugins.sbt
@@ -1,3 +1,3 @@
 resolvers += Resolver.url("scalasbt releases", new URL("http://scalasbt.artifactoryonline.com/scalasbt/sbt-plugin-releases"))(Resolver.ivyStylePatterns)
 
-addSbtPlugin("org.scala-sbt" % "sbt-android-plugin" % "0.6.1")
+addSbtPlugin("org.scala-sbt" % "sbt-android-plugin" % "0.6.2-SNAPSHOT")

sbt-android-plugin のバージョンを 0.6.2-SNAPSHOT に変えることで、さっき編集したバージョンを読みにいかせます(この説明は自信がない…)。

編集したら、もう一度テストを実行します。

> reload
> clean
> tests/clean
> android:package-debug
> android:install-emulator
> tests/android:package-debug
> tests/android:install-emulator
> tests/android:test-emulator

今度はうまくいくはず。

[info] 
[info] my.android.project.tests.ActivityTests:.
[info] my.android.project.tests.AndroidTests:..
[info] Test results for InstrumentationTestRunner=...
[info] Time: 1.509
[info] 
[info] OK (3 tests)
[info] 
[info] 
[success] Total time: 4 s, completed 2012/04/11 17:25:30

結論

正直、このやり方でいいのかわかりません。何しろ ScalaJava も sbt もにわかなので(だからこそ5日も悩みました)。私的には、AndroidInstall.scala を上記のように編集したことで、管理されたソースである TR.scala がうまく tests プロジェクトに対してエクスポートされたのでは、と考えています。
間違った知識を流布してもいけないので、おかしなところはご指摘いただけると幸いです。