AndroidでContextが必要な機能のテストコードを簡単に書こうとしてとても時間がかかった話

# はじめに
  事の始まりは`res/raw`に置いたJsonファイルの読み込み処理が正しくできているか？というテストコードを書きたかったことです。
  エミュレータを起動していちいち確認するのが嫌だったのでUnitテストを書きたかったのですが、すぐ書けるだろと慢心していたら完全に詰まったのでそこで調べたことをまとめようと思います。
  初歩的で自分の無知をさらけ出す羽目になるのですが、今後同じような境遇の方の助けになればと思います。。。

# 対象読者
  - Androidのテスト書こうとググってコピペしたけど`AndroidJUnit4.class`なんて使えない
  - Androidのテスト書こうとググってコピペしたけど`InstrumentationRegistry`が使えない
  - `AndroidTestCase`を継承するのだ！とあるけどDeprecatedですどうすれば・・
  - Contextが必要なテストコードを楽にさっさと書きたい

# 作業環境
  - Android Studio3.1
  - com.android.tools.build:gradle:3.0.1
  - junit:4.12
  - com.android.support.test:runner:1.0.1

# 結論
  Unitテストで`Context`使う場合は`Robolectric`が一番楽だと思います。
以下、サンプルです。

  ```build.gradle
  android {
    ...
    // AndroidStudio3を使用している場合はこのテストオプションが必要
    // 詳細は http://robolectric.org/getting-started/
    testOptions {
        unitTests {
            includeAndroidResources = true
        }
    }
  }
  dependencies {
    // 2018年1月27日時点の最新版
    testImplementation "org.robolectric:robolectric:3.6.1"
  }
  ```
  build.gradleを設定したら対象のテストクラスで以下のようにして`Context`を取得できます。

  ```java
  // 1. RobolectricTestRunnerを指定する
  @RunWith(RobolectricTestRunner.class)
  public class HogeTest() {

    @Test
    public void read() {
      // 2. contextを取得する
      Context context = RuntimeEnvironment.application;
      // 以降はcontext.getResourceやcontext.getStringなどでres情報にアクセスできる
    }
  }
  ```

# この記事の本題
  長い上にテストに精通した方なら「はあ？そんなことも知らねーのかよ」となる内容なので暖かい目で読んでください・・・

## そもそもなぜ苦労したのか
  最初、この作業に取りかかるとき「Android test context」といったワードでググり、`src/test`配下にテストクラスを作ろうとしていました。
  小難しい方法は嫌だったので、Mock作って`when`で`getResource`が呼ばれた時ウンヌンみたいなのは辛いから、とにかく`Context`さえ取れて一番手っ取り早い方法がいい、といった具合で調べました。
  そうすると以下のようなことが起こります。

  1. `@RunWith`で`AndroidJUnit4.class`と指定しろと色々なサイトに書いてあるのに、そもそも`JUnit4`しか選べない。
  2. `Context`は`InstrumentationRegistry`で取得可能とあるけど`InstrumentationRegistry`なんてクラスはないしimportできない
  3. テストは`AndroidTestCase`を継承し`getContext`すればOK!とあるけどこのクラスはすでにDeprecatedになっていてどうすれば・・
  stack overflowで`AndroidJUnit4.class`は廃止されて`JUnit4.class`を使うんだよとかありましたが、`developer.android.com`では普通に`AndroidJUnit4`使っている例があるのでそんなはずはないと思いつつも、でも`build.gradle`で色々試しても上手くいきませんでした。

## 解決に辿り着いたきっかけ
  しばらく考えて、ふと気になって`androidTest`の`ExampleInstrumentedTest.java`(最初に自動生成されるコード)を開いたら`AndroidJUnit4.class`を指定し`InstrumentationRegistry`を使って`Context`を取得していました。  
  この時点で「ああ、そうか・・なんか色々理解してなさそうだな」と私の中で色々と腑に落ちることになりました。

## Local Unit TestとInstrumented Tests
  再度、この2つのテストについておさらいすることにしました。
  `developer.android.com`のページにて`Local Unit Test`と`Instrumented tests`について説明されています。

   - https://developer.android.com/studio/test/index.html 　
  
  `Local unit tests`は`src/test`、`Instrumented tests`は`src/androidTest`とそれぞれディレクトリが分かれており、UnitテストはJVM上で実行され、Instrmentedテストはハードウェアデバイスやエミュレータ上でそれぞれ実行されます。そして、Instrmentedテストでは`Context`などUIに関するアクセスを提供する`Instrumentation APIs`が使えると書かれています。
  では`Instrumentation APIs`はどうやったら使えるのかというと、テスト用の`AndroidManifest.xml`に指定する必要があり、それは`Gradle`がビルド時に自動生成してくれるようで、我々利用者側で意識する必要はありません。`src/androidTest`で作成したテストクラスでのみ`Instrumentation APIs`を使えるのだと思われます。
  また、最初に詰まった`Context`を取得するために必要であろう`AndroidJUnit4.class`や`InstrumentationRegistry`は`android.support.test`パッケージで定義されており、これらはUnitテストのディレクトリ(`src/test`)ではimportできません。
**（実際にはimportだけなら無理矢理できましたので次節で説明します。)**
  かなり恥ずかしい話ですが、私は`androidTest`にはUIテストを、`test`にはUnitテストを書くというだいぶ大雑把な理解しかしていませんでした。
  そもそも`AndroidJUnit4`や`InstrumentationRegistry`が`test`パッケージで使えないのは当たり前でした。

## なぜLocal Unit TestではAndroidJUnit4やInstrumentationRegistryが使えない？
  それぞれ`AndroidJUnit4`は`android.support.test.runner`、`InstrumentationRegistry`は`android.support.test`のクラスです。
  これらのクラスが`src/test`配下に作成したUnitテストクラスでimportできない理由は、依存先が異なるためです。
  `build.gradle`の`dependencies`では、`testImplementation(またはtestCompile)`と`androidTestImplementation(またはandroidTestCompile)`の２種類が指定できます。この指定の違いは以下の通りです。

  - testImplementation: `Local unit tests`に依存させる指定で、`src/test`で作成したテストクラスで使用可能
  - androidTestImplementation: `Instrmented Tests`に依存させる指定で、`src/androidTest`で作成したテストクラスで使用可能
 
  サンプルのbuild.gradleを例に、どの指定がどこに影響するか示します。

  ```build.gradle
  dependencies {
    ...

    // 以下の指定はUnitテストに依存。src/testディレクトリで使用可能。ただ、junit4は特殊でInstrmentedテストでも利用可能
    testImplementation 'junit:junit:4.12'
    testImplementation "org.robolectric:robolectric:3.6.1"

    // 以下の指定はInstrmentedテストに依存。src/androidTestディレクトリで使用可能
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
  }
  ```
  ということは、例えば`androidTestImplementation 'com.android.support.test:runner:1.0.1'`を
  `testImplementation 'com.android.support.test:runner:1.0.1'`
  と書き換えればUnitテストでも`AndroidJUnit4`や`InstrumentationRegistry`がimportできる理屈になるはずです。実際、書き換えたらimportできました。 
**ただ、importできるだけでテスト実行時にエラーが出ました。これは`Instrumentation APIs`がUnitテストでは使えないためだと思われます。**
  基本的に`test:runner`は`Instrumentation APIs`とセットで使用する必要があるという理解であっていると思います。そもそもこんな指定ができないよう、わざわざUnitテストとInstrmentedテストを区別しているのでこの結果は当たり前でした。
  公式ではありませんが、stack overflowに同じような疑問を持つ方がいて回答が参考になりました。
  https://stackoverflow.com/questions/37821148/why-cannot-i-import-androidjunit4-and-activitytestrule-into-my-unit-test-class/37840321

## じゃあUnitテストでContext使うには？
  ここまでの話を理解すれば簡単です。私はAndroidのテスティングフレームワークが色々あって面倒臭いなと思っていました。しかし、こうした事情が理解できれば色々ある理由も分かるしどれを使えばいいかも見えてきます。今回のケースに一番当てはまるのは`Robolectric`かと思います。
  ここで最初に書いたまとめに辿り着きます。ほんの少しの追記で`Context`をUnitテストで扱うことができるようになっていい感じです。
  ただし、今回はAndroidStudio3系でかつres/rawのファイル読込テストをしたかったため、そのまま実行しようとしたら  `android.content.res.Resources$NotFoundException`が発生しました。
  これはAndroidStudio3系ではデフォルトで`AAPT2`がONになっていることが原因で、リソースファイルをテストで扱う場合は注意が必要でした。
  このエラーは、まとめに書いたオプションを指定することで解消します。
  なお、Droidkaigi2017のカンファレンスアプリでも同様の方法でContextを取得していたので、最初からそっちを参考にしていればもっと早く解決できた気がします。。
  ただ、自分があやふやな理解であることを自覚できたため、まとまった時間をとって改めて調査できたのは良かったかなと思います。

# 2018年11月18日 追記
androidxに移行したら`RunWith(AndroidJUnit4::class)`と`InstrumentationRegistry`がdeprecatedになりました。
それぞれ定義に飛んでみると`AndroidJUnit4`は`androidx.test.ext.junit.runners.AndroidJUnit4`にしてくれとあります。
build.gradleの定義を以下の通り書き換えます。

```
// 修正前
androidTestImplementation 'androidx.test:runner:1.1.0'
// 修正後
androidTestImplementation 'androidx.test.ext:junit:1.0.0'
```

修正内容をsyncしてAndroidJUnit4をimportしなおせばOKです。
`InstrumentationRegistry`は`androidx.test.platform.app.InstrumentationRegistry`にしてくれとあります。

```kotlin
// 修正前
val context = InstrumentationRegistry.getContext()
// 修正後
val context = InstrumentationRegistry.getInstrumentation().context
```

これでdeprecatedも解消されテストが通るようになりました。