LoginSignup
6
5

More than 5 years have passed since last update.

Instrumented Testの実行中のみアニメーションを無効化する

Last updated at Posted at 2018-02-12

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で囲むかはお好みで。

NoAnimationTestRule.kt
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を足すだけで良い。

MainActivityTest.kt
@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にしてしまう(過激案)
6
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5