= テスト環境について = Androidには立派なテスト環境があり、「これだけやっとけば大丈夫だろ」と言わんばかりのものらしいが、これは間違い! 。。。いや間違いではないのだが、主軸にしてはならない。 Eclipseを開発環境とするならば、Eclipse上でささっとテストができるべきである。 これがまず第一。その上で、テストしにくいGUI部分をこの環境で行うのなら完璧というところだろう。 わざわざテスト用のアプリを作成し、それを決して速いとは言えないエミュレータにインストールし、それからもろもろのテスト操作を行うというのでは、 嫌になること請け合いである。 '''テストは楽しくなければならない'''のである。 == GUIとビジネスロジックを分割する == Android云々、テスト環境云々以前に、まずこの技術を習得しなければならない。 そもそもGUIは、どういう方法をとってもテストしにくいものなのだから、GUIコードとビジネスロジックコードが完全に分割できるように し、個別のテストが可能でなければならない。 「Androidのテスト環境」にいくら習熟しようともこの点を習得することはできない。 世にあるあまたのサンプルプログラムは、この点を無視したものが非常に多い。 すなわち、プレゼンテーションとビジネスロジックがごちゃまぜなのである。 そこを認識しているか認識していないかの違いで、技術者としての力量がわかるものである。 さらに、別途記述しているが、特にリソースを使ったGUIを作成した場合、それをjarライブラリとしてまとめることはできないし、 そうであれば簡単に使いまわすことができない。これがしたければ、以下のいずれか、あるいは両方の方策が必要になる。 * GUIとビジネスロジックを完全に切り離す * GUI構築においてリソースを一切使用しない == ADTプラグインを何とかする == Eclipse上のADTプラグインは便利なものだが、一つ困ったことがある。 プロジェクトの実行に必要なものを有無をいわさず取り込んでapkにしてしまうことである。 これがなぜ困るのかと言えば、安易にテスト用のコードを混ぜることができないから。 これがゆえにAndroid「純正」のテストは、わざわざ別プロジェクトを作成することになっている。 この環境は、同じプロジェクトにテスト用コードがあるなどとは夢にも思っていないのである。 これでは(私のやり方としては)困るのである。 方策は二つある。 * ADTプラグインを使わない。プロジェクトを普通のJavaプロジェクトとする。 * ADTプラグインを使うが、そのapkビルド機能は使わない。 しかし、いずれにしてもantは利用しなければならないので、結局前者の方が楽ではある。 次はbuild.xmlをどこからか調達することが課題である。 == build.xmlサンプル == ググってみると、build.xmlのサンプルもあるにはあるが、対応バージョンが古く、現在のSDK(2.2)ではそのままでは使用できない。 これらを参考にして、修正したものが以下である。 {{{ Creating output directories if needed... Generating R.java / Manifest.java from the resources... Compiling aidl files into Java classes... Converting compiled files and external libraries into ${DEX_FILE}... Packaging resources and assets... Packaging ${APK_DEBUG}, and signing it with a debug key... Packaging ${APK_UNSIGNED} for release... It will need to be signed with jarsigner before being published. Installing ${APK_DEBUG} onto default emulator... Installing ${APK_DEBUG} onto default emulator... Uninstalling ${APP_PACKAGE} from the default emulator... }}} == ログ出力に注意 == Androidでのログ出力はandroid.util.Logクラスのstaticメソッドで行うことになっているが、これまた多くのウェブサイトにて単純にデバッグ用ログの出力を {{{ Log.d(tag, message); }}} などと平然と記述している例が多いのだが、これは間違い! {{{ if (Log.isLoggable(tag, Log.DEBUG) { Log.d(tag, message); } }}} としなければならない。 参考サイトとしては以下。 [[http://hackmylife.net/2010/09/androidutillog.html]] ただし、毎回毎回このようなコードを書くのが苦痛になるのはあたりまえである。 なんらかの自作コードでAndroidのロギングユーティリティの呼び出しをラップするのが当然だろう。 == Android-APIに注意 == AndroidのAPIを使わなければAndroid用アプリは当然組めないのだが、逆説的であるが'''これを使ってはならない'''。 これらのAPIをラップしたものを使用するべきである。 前項のロギングユーティリティが良い例だが、これらはstaticメソッドであるため、これを直接使用してしまうと、どうあってもテストはエミュレータ上で行うしかなくなってしまう(あくまで一般的な話。これでもテスト可能な特殊な環境もあるらしいが)。 ひどいことにSDKに含まれているAPIすなわちandroid.jarというファイルには中身がない。 これは単にアプリがコンパイルできるようにするためだけのモックでしかないのである。 例えば、android.util.Logクラス逆コンパイルしてみると以下のようなコードになる。 {{{ /* */ package android.util; /* */ /* */ public final class Log /* */ { /* */ public static final int VERBOSE = 2; /* */ public static final int DEBUG = 3; /* */ public static final int INFO = 4; /* */ public static final int WARN = 5; /* */ public static final int ERROR = 6; /* */ public static final int ASSERT = 7; /* */ /* */ Log() /* */ { /* 4 */ throw new RuntimeException("Stub!"); } /* */ public static int v(String tag, String msg) { throw new RuntimeException("Stub!"); } /* */ public static int v(String tag, String msg, Throwable tr) { throw new RuntimeException("Stub!"); } /* */ public static int d(String tag, String msg) { throw new RuntimeException("Stub!"); } /* */ public static int d(String tag, String msg, Throwable tr) { throw new RuntimeException("Stub!"); } /* */ public static int i(String tag, String msg) { throw new RuntimeException("Stub!"); } /* */ public static int i(String tag, String msg, Throwable tr) { throw new RuntimeException("Stub!"); } /* */ public static int w(String tag, String msg) { throw new RuntimeException("Stub!"); } /* */ public static int w(String tag, String msg, Throwable tr) { throw new RuntimeException("Stub!"); } /* */ public static native boolean isLoggable(String paramString, int paramInt); /* */ /* */ public static int w(String tag, Throwable tr) { throw new RuntimeException("Stub!"); } /* */ public static int e(String tag, String msg) { throw new RuntimeException("Stub!"); } /* */ public static int e(String tag, String msg, Throwable tr) { throw new RuntimeException("Stub!"); } /* */ public static int wtf(String tag, String msg) { throw new RuntimeException("Stub!"); } /* */ public static int wtf(String tag, Throwable tr) { throw new RuntimeException("Stub!"); } /* */ public static int wtf(String tag, String msg, Throwable tr) { throw new RuntimeException("Stub!"); } /* */ public static String getStackTraceString(Throwable tr) { throw new RuntimeException("Stub!"); } /* */ public static int println(int priority, String tag, String msg) { throw new RuntimeException("Stub!"); /* */ } /* */ } }}} つまり、何を呼び出しても"Stub!"というメッセージを出力する例外が発生するだけなのだ。 さらにひどいのは、こんなことをするのであれば、せめてモックの内容を変更できるようにしておいてくれればよいものを、 そんな気も無いらしいということ。 つまり、APIを直接呼び出してしまうと、そのプログラムはどうあってもエミュレータか実機の上でしかテストできないのである。 '''APIを使用してはならないのだ!'''