AndroidJava/UnitTestは起動が早い

端末にインストールするよりも起動が早いので(2019-04-14)


(古い記事です。当時のお話として、参考まで)


とりあえず動かしてみましょう

単体試験やテストの自動化とか、本来?の目的以外にも、
端末にアプリをインストールするよりもUnitTestの方がビルドが「早い」ので便利です。
Android Studio作業中に何か試したいコードが出たときに、
(GUIな部分が必要なければ)ビルドして端末にインストールするよりも
UnitTestで実行して System.out.println した方が
確認までにかかる時間が短縮できます。

プロジェクトを新規作成すると、こんな例が自動でついてくるのではないでしょうか?
(たぶん Ctrl + Shift + f で「ExampleUnitTest」を検索すれば見つかるのでは)

public class ExampleUnitTest {
    @Test
    public void addition_isCorrect() throws Exception {
        assertEquals(4, 2 + 2);
    }
}

assertEquals は一旦、「//」とかで、コメントアウトでもしておいて、
試しに「System.out.println("Hello world!");」とか書いて実行すれば
CUIで、それっぽい Hello world! が表示されると思います。

実行する時は、
ツールバーの再生ボタン(を選ぶ前に、appではなくテストを選択して)から実行でも良いですが、
ソースコード上で右クリックして「Run」を選ぶのが早そうです。
(ショートカットキーを使えばもっと早いですが)
ちなみに私はよく、試験をやろうとしてアプリを、アプリに戻ろうとして試験を、間違えてビルドしてしまいます...

どうでしょう、端末やエミュレータで動かすよりも、 UnitTestでハローワールドした方が、かなり早くなかったですか?
これで何か実験する時に(例:あれ、論理積だから a &= b; で有ってるよな?とか確認する際に)
とりあえずコード書いて 「System.out.println("a:" + a);」みたいに出力すれば、早くて便利です。

ContextもUnitTestで呼べます

そんなわけで、
以下、私の環境 AndroidStudio 3.2 でUnitTestした例です。

https://developer.android.com/studio/test/?hl=ja
で「ローカル ユニット テスト」と「インストルメント化されたテスト」の説明がありますが、
ローカルユニットテストでも Context を使いたい場合は
robolectric を使えばいいそうです。

Context、たとえば res/values/strings.xml とかに置いた文字を
context.getString(R.string.なんとか) で拾う処理がUnitTestでもできるようになります。

http://robolectric.org/getting-started/ を参考にして、例として、
(バージョンは今回試したものなので、適宜読み替えて下さい)
該当する build.gradle に以下を追記
multidex はコンパイル時にDex絡みのエラーが出る場合に要ります。
(出なければ、要らないのかな?)

android {
  testOptions {
    unitTests {
      includeAndroidResources = true
    }
  }
}
 
dependencies {
  testImplementation 'junit:junit:4.12'
  testImplementation 'org.robolectric:robolectric:3.8'
  testImplementation 'org.robolectric:multidex:3.4.2'
}

そして、Android Studio がデフォルトで作成してくれた
ExampleUnitTestクラスを同じフォルダにコピペして(名前は適当に変えて)、
public class 〜 の直前に一筆

@RunWith(RobolectricTestRunner.class)

を書き加えました。

@RunWith(RobolectricTestRunner.class)   ←※こんな感じで
public class ExampleUnitTest {
   @Test
   public void addition_isCorrect() throws Exception {
   (以下、とくに変更なし)

そして、右クリックで「Run (いま更新したクラス)」を実行すると
「junit version 3.8 or later expected」
のエラーが出ると思います。
(出ませんか?おや?...出た人だけ続きをお読み下さい)

私の環境だと、前述の通り「testImplementation 'junit:junit:4.12'」
としているのですが、それよりも前に
AndroidStudioデフォルトの Junit(3.8) が読み込まれてしまう、らしいのです。

上記「robolectric junit version 3.8 or later expected 」でWeb検索すると
いくつか方法はあるようで、とにかく読み込み順を変えます。

  • 案1:[Run]-[Edit Configurations] で該当するUnitTestのコンフィグを開いて、
    [Configuration]-[VMoptions] で "-classpath" 指定で先に新しい JUnit を読み込ませる
  • 案2:[File]-[Project Structure]から
    [Modules]-[Dependencies] で新しいJUnitを上に持ってくる。
    (たぶん include=*.jar より上にすればデフォルトの前になる?)

私の場合は、後者を選んでみました。
これでとりあえず、Runできるようにはなりました。
これで、テスト用のJUnitクラスで
「RuntimeEnvironment.application」を呼べるようになったので、
getApplicationContext()するなりなんなりできるようになりました。

@RunWith(RobolectricTestRunner.class)
public class MyUnitTest {
 
   @Test
   public void test() throws Exception {
       Context context = RuntimeEnvironment.application.getApplicationContext();
       // 自分で作ったクラスにContextが渡せるようになる
       MyClass my_class = new MyClass(context);
       // 以下、my_class でやってみたい処理を書いていく

Contextも UnitTestから呼べるって知るまで、端末で確認していました。
起動の早さが、わりと、やる気に影響するんですよね...