AndroidでJSONを操作するところのUnit Testを書こうと思ったら、Robolectricが動かなくて困ったのでメモ
環境
- Android Studio 4.0.1
- Robolectric 4.4
- targetSdkVersion 30
Robolectric 4.3.1からはリリースノートにある通り、API Level 29が対象の時にはJava 9が必須になっている
そしてAndroid Studio 4.0に同梱されているのはJava 8系なので動かない
解決の方法としては以下の2通りが考えられる
- Java 9以降のJDKをインストールする
- Robolectricの対象API Levelを下げる
個人的にはAndroid Studio以外にインストールするものがあるのは面倒だし、環境を作り直すときに確実に忘れるので 対象API Levelを下げる
方向で対処する
Robolectricで対象のAPI Levelを設定するには以下のような方法がある
- @Config annotationで指定する
- robolectric.propertiesで指定する
- RobolectricTestRunnerをextendする
ただどれも面倒だと思ってしまったので、今回は build.gradle.kts
を編集する
公式サイトにあるようにRobolectricは targetSdkVersion
に指定されているバージョンを対象にしている
このため、例えばbuild.gradle.ktsが以下のようになっているとAPI Level 29が使用される
android {
defaultConfig {
targetSdkVersion(29)
}
}
であるならば、testの実行時だけtargetSdkVersionを書き換えれば、他のAPI Levelを使用するよう設定できる
どうするかというと
android {
defaultConfig {
targetSdkVersion(29)
}
testOptions {
defaultConfig {
targetSdkVersion(28)
}
}
}
これでUnit TestとInstrumented Testの両方に作用する
Unit Testだけに絞るなら以下のようにネストすれば良い
android {
defaultConfig {
targetSdkVersion(29)
}
testOptions {
unitTests {
defaultConfig {
targetSdkVersion(28)
}
}
}
}
ちょっと作りたいものがあるので, AndroidからDocker Desktop for Macで動かしているPostgreSQLにJDBC Driverで接続できるか実験
環境
- Android Studio 3.5.2
- Android Emulator 29.2.1
- Nexus 5
- PostgreSQL JDBC Driver 42.2.8
- postgres:11.5 Docker image
実験用ソース
Android Studioの Start a new Android Studio Project
から, Empty ActivityでMinimum API level 23なプロジェクトを作る
これに必要な分をちょっとだけいれたものを使う
まずはapp/build.gradleのdependenciesにドライバを追加する
dependencies {
// 略
implementation 'org.postgresql:postgresql:42.2.8'
// 略
}
次に適当な接続してSQLを実行するコードを, MainActivity.ktに追加する
override fun onResume() {
super.onResume()
thread {
val c = DriverManager.getConnection("jdbc:postgresql://10.0.2.2/postgres?user=postgres&password=password")
val s = c.createStatement()
val r = s.executeQuery("CREATE TABLE IF NOT EXISTS sample (id SERIAL PRIMARY KEY, number INTEGER)")
r.close()
s.close()
c.close()
}
}
ネットワークアクセスするのでパーミッションの記述をAndroidManifest.xmlに追加する
+ <uses-permission android:name="android.permission.INTERNET" />
Emulator と実機の比較
最近の端末を持っていないのでエミュレータを使うとして, 実機と同じように動作するかテストする
Android 6.0.1がインストールされたNexusとAPI 23のエミュレータで実行したところ, 同じように以下のExceptionが出力された
カスタマイズして出荷されている可能性があるので一概に言えないが, とりあえずエミュレータが実機と同じように動作すると判断する
E/AndroidRuntime: FATAL EXCEPTION: Thread-147
Process: com.example.sample, PID: 2812
java.sql.SQLException: No suitable driver
at java.sql.DriverManager.getConnection(DriverManager.java:186)
at java.sql.DriverManager.getConnection(DriverManager.java:144)
at com.example.sample.MainActivity$onResume$1.invoke(MainActivity.kt:19)
at com.example.sample.MainActivity$onResume$1.invoke(MainActivity.kt:8)
at kotlin.concurrent.ThreadsKt$thread$thread$1.run(Thread.kt:30)
実験
いろいろなバージョンで試してみる
とりあえず上の実験でAPI 23はダメだったので, それ以降を順次試す
API 24, 25はクラスが見つからないのでダメ
java.lang.NoClassDefFoundError: Failed resolution of: Ljava/time/Duration;
Caused by: java.lang.ClassNotFoundException: Didn't find class "java.time.Duration" on path: DexPathList[[zip file "/data/app/com.example.sample-1/base.apk"],nativeLibraryDirectories=[/data/app/com.example.sample-1/lib/x86_64, /system/lib64, /vendor/lib64]]
API 26から29はExceptionがthrowされているものの, SQLを実行した結果なのでOK
org.postgresql.util.PSQLException: No results were returned by the query.
追加実験 (追記: 2019/12/30)
API 24のExceptionはJava 8およびAPI 26で追加された java.time.Duration
を使っているからダメなだけで,
DriverをJava 7用にしたら問題なかったのではと思い立ったので実験
app/build.gradleのdependenciesを以下のように書き換える
dependencies {
// 略
- implementation 'org.postgresql:postgresql:42.2.8'
+ implementation 'org.postgresql:postgresql:42.2.8.jre7'
// 略
}
他は変更せずに動かしてみるとAPI 26以降と同じ org.postgresql.util.PSQLException
が投げられたので接続OK
結論
Android 7.0 NougatからJDBC DriverでPostgreSQLに接続できる
なおAndroid 7.0, 7.1 はJava 7用を使う必要があるが, Android 8.0 OreoからはJava 8用で構わない
ただし端末による