Espressoのセットアップ手順の冒頭 "Set up your test environment" にもある通り、アニメーションをオフにすることが推奨されている。
https://developer.android.com/training/testing/espresso/setup.html
To avoid flakiness, we highly recommend that you turn off system animations on the virtual or physical devices used for testing. On your device, under Settings > Developer options, disable the following 3 settings:
- Window animation scale
- Transition animation scale
- Animator duration scale
これをやるだけで、EspressoやUI Automatorのテスト成功率はグッと上がる。
ただ、この設定の変更手順は思った以上にめんどくさいので、自動化しておきたいところ。
adb shell
adb shell settings put global window_animation_scale 0
adb shell settings put global transition_animation_scale 0
adb shell settings put global animator_duration_scale 0
ちなみに、現在の設定値は以下で取得できる。
adb shell settings get global window_animation_scale
adb shell settings get global transition_animation_scale
adb shell settings get global animator_duration_scale
メリット
* 特別な設定が不要。
デメリット
- 問答無用で0に上書きしてしまうので、元に戻せない。
私のように、Nexus 6Pを常用していて、開発でも使うようなケースではこの方法は使いたくない。
put/getを組み合わせれば「0にする・復元する」のバッチは作れそうだが、バッチの実行を自動化するのがめんどくさい感じ。
connectedDebugAndroidTestはAndroid Studio経由のときは呼ばれないっぽいし……ということで、次の案へ。
TestRuleを作る(API24以降)
こんな感じで、テストの前後でコマンドを実行する。
get/putをtryで囲むかはお好みで。
import android.support.test.InstrumentationRegistry
import android.support.test.uiautomator.UiDevice
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
class NoAnimationTestRule : TestRule {
override fun apply(base: Statement?, description: Description?): Statement {
return object : Statement() {
override fun evaluate() {
val uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
val backupAnimationScale = getAnimationScale(uiDevice)
putAnimationScale(uiDevice, disableAnimationScale)
try {
base?.evaluate()
} finally {
putAnimationScale(uiDevice, backupAnimationScale)
}
}
}
}
data class AnimationScale(
val windowAnimationScale: String
, val transitionAnimationScale: String
, val animatorDurationScale: String
)
val disableAnimationScale = AnimationScale("0.0", "0.0", "0.0")
fun getAnimationScale(uiDevice: UiDevice): AnimationScale? {
return try {
val windowAnimationScale = uiDevice.executeShellCommand("settings get global window_animation_scale")
val transitionAnimationScale = uiDevice.executeShellCommand("settings get global transition_animation_scale")
val animatorDurationScale = uiDevice.executeShellCommand("settings get global animator_duration_scale")
AnimationScale(windowAnimationScale, transitionAnimationScale, animatorDurationScale)
} catch (e: Exception) {
e.printStackTrace()
null
}
}
fun putAnimationScale(uiDevice: UiDevice, animationScale: AnimationScale?) {
if (animationScale != null) {
try {
uiDevice.executeShellCommand("settings put global window_animation_scale ${animationScale.windowAnimationScale}")
uiDevice.executeShellCommand("settings put global transition_animation_scale ${animationScale.transitionAnimationScale}")
uiDevice.executeShellCommand("settings put global animator_duration_scale ${animationScale.animatorDurationScale}")
} catch (e: Exception) {
e.printStackTrace()
}
}
}
}
テストコードにはRuleを足すだけで良い。
@RunWith(AndroidJUnit4::class)
class MainActivityTest {
@Rule
@JvmField
val rule: ActivityTestRule<MainActivity> = AplinActivityTestRule(MainActivity::class.java, false, false)
@Rule
@JvmField
val noAnimationTestRule = NoAnimationTestRule()
@Test
fun test() {
// test code
}
}
ちなみに、API23以下はダメで24以降が使える理由は、この変更の影響っぽい。
https://android.googlesource.com/platform/frameworks/base/+/b52c7330d986e62812fd7c1b77020629e8ff7930
BugとChange-Idはどこを見れば良い?
(わかる人教えてください)
メリット
- テスト終了時に設定が元に戻るので、常用端末を使ったテストがしやすくなる
- コマンドを手動で実行する必要がなくなる
デメリット
- API24以降でしか動かなかった
→ Build.VERSION.SDK_INTでget/set内をガードする(妥協案)
→ minSdkVersionを24にしてしまう(過激案)