使用FragmentScenario进行仪器测试

时间:2018-12-05 09:42:02

标签: android android-testing

我正在尝试使用androidx测试库的新FragmentScenario API进行本地测试和工具测试(androidTest)。该api在本地环境中工作正常,但在仪器测试中会出现错误: java.lang.AssertionError:活动永远不会变成请求状态“ [RESUMED,DESTROYED]”(上一个生命周期转换=“ PRE_ON_CREATE”)

帮助我进行仪器测试(androidTest)

请检查完整的错误详细信息:

java.lang.AssertionError: Activity never becomes requested state "[RESUMED, DESTROYED]" (last lifecycle transition = "PRE_ON_CREATE")
at androidx.test.core.app.ActivityScenario.waitForActivityToBecomeAnyOf(ActivityScenario.java:228)
at androidx.test.core.app.ActivityScenario.launch(ActivityScenario.java:198)
at androidx.fragment.app.testing.FragmentScenario.internalLaunch(FragmentScenario.java:169)
at androidx.fragment.app.testing.FragmentScenario.launchInContainer(FragmentScenario.java:160)
at com.techzis.avatr.LoginFragmentTest1.dummyTest(LoginFragmentTest1.kt:26)
at java.lang.reflect.Method.invoke(Native Method)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at androidx.test.ext.junit.runners.AndroidJUnit4.run(AndroidJUnit4.java:104)
at org.junit.runners.Suite.runChild(Suite.java:128)
at org.junit.runners.Suite.runChild(Suite.java:27)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
at androidx.test.internal.runner.TestExecutor.execute(TestExecutor.java:56)
at androidx.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:388)
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:2152)

Instrument Testing(androidTest)代码为:

@RunWith(AndroidJUnit4::class)
class LoginFragmentTest1 {
    @Test
    fun dummyTest() {
        val scenario = launchFragmentInContainer<LoginFragment>()
        onView(ViewMatchers.withId(R.id.user_name)).perform(ViewActions.typeText("Hello World!"))
        onView(ViewMatchers.withId(R.id.user_name)).check(matches(withText("Hello World!")))
    }

}

本地单元测试代码为:

@RunWith(AndroidJUnit4::class)
@Config(application = MyApplication::class, shadows = [ShadowAndroidXMultiDex::class])
class LoginFragmentTest2 {
    @Test
    fun dummyTest() {
        val scenario = launchFragmentInContainer<LoginFragment>()
        onView(ViewMatchers.withId(R.id.user_name)).perform(ViewActions.typeText("Hello World!"))
        onView(ViewMatchers.withId(R.id.user_name)).check(matches(withText("Hello World!")))
    }

}

应用程序级别的build.gradle文件为:

apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

apply plugin: 'kotlin-kapt'

apply plugin: "androidx.navigation.safeargs"

android {

    compileSdkVersion 28

    defaultConfig {
        applicationId "com.example"
        minSdkVersion 18
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        vectorDrawables.useSupportLibrary = true
        multiDexEnabled false
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        testInstrumentationRunnerArguments clearPackageData: 'true'
    }

    buildTypes {
        release {
            shrinkResources true
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

    dataBinding {
        enabled = true
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    androidExtensions {
        experimental = true
    }

    lintOptions {
        checkReleaseBuilds false
        // Or, if you prefer, you can continue to check for errors in release builds,
        // but continue the build even when errors are found:
        abortOnError false
    }

    kapt {
        javacOptions {
            option("-Xmaxerrs", 1000)
        }
    }

    testOptions {
        unitTests.includeAndroidResources = true
        execution 'ANDROIDX_TEST_ORCHESTRATOR'
    }


    configurations.all {
        resolutionStrategy {
            force 'com.google.code.findbugs:jsr305:3.0.2'
            force 'org.jetbrains.kotlin:kotlin-reflect:1.2.71'
        }
    }
    sourceSets {
        test { java.srcDirs += "$projectDir/src/testShared" }
        androidTest {
            java.srcDirs += "$projectDir/src/testShared"
            resources.srcDirs += "$projectDir/src/test/resources"
        }
    }
}

dependencies {
    def lifecycle_version = "2.0.0"
    def fragment_version = "1.1.0-alpha01"
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.multidex:multidex:2.0.0'
    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation "androidx.fragment:fragment:$fragment_version"
    implementation 'com.google.android.material:material:1.0.0'
    implementation 'androidx.recyclerview:recyclerview:1.0.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.1'
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.1'
    implementation 'com.squareup.retrofit2:retrofit:2.4.0'
    implementation 'com.squareup.retrofit2:converter-moshi:2.4.0'
    implementation "com.squareup.moshi:moshi-kotlin:1.8.0"
    implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2'
    implementation 'com.squareup.okhttp3:logging-interceptor:3.9.1'
    implementation 'com.github.bumptech.glide:glide:4.8.0'
    kapt 'com.github.bumptech.glide:compiler:4.8.0'
    kapt "com.android.databinding:compiler:$gradle_version"
    implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
    implementation "android.arch.navigation:navigation-fragment-ktx:$nav_version"
    implementation 'com.github.florent37:diagonallayout:1.1.1'

    testImplementation 'androidx.test:core:1.0.0'
    testImplementation 'org.robolectric:robolectric:4.1-alpha-1'
    androidTestImplementation 'androidx.test:runner:1.1.0'
    testImplementation 'androidx.test:runner:1.1.0'
    androidTestImplementation 'androidx.test.ext:junit:1.0.0'
    testImplementation 'androidx.test.ext:junit:1.0.0'
    androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.0'
    testImplementation 'androidx.test.espresso:espresso-intents:3.1.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
    testImplementation 'androidx.test.espresso:espresso-core:3.1.0'
    androidTestImplementation 'androidx.test.ext:truth:1.0.0'
    testImplementation 'androidx.test.ext:truth:1.0.0'
    androidTestImplementation 'org.hamcrest:hamcrest-library:1.3'
    testImplementation 'org.hamcrest:hamcrest-library:1.3'
    androidTestImplementation "io.mockk:mockk-android:1.8.13.kotlin13"
    testImplementation "io.mockk:mockk:1.8.13.kotlin13"

    androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'

    androidTestImplementation "android.arch.navigation:navigation-testing:$nav_version" // Test helpers for navigation
    androidTestImplementation "androidx.fragment:fragment-testing:$fragment_version"
    testImplementation "androidx.fragment:fragment-testing:$fragment_version"
    androidTestImplementation "androidx.arch.core:core-testing:$lifecycle_version" // Test helpers for LiveData
    testImplementation "androidx.arch.core:core-testing:$lifecycle_version" // Test helpers for LiveData

    androidTestImplementation 'com.squareup.okhttp3:mockwebserver:3.10.0'
    testImplementation 'com.squareup.okhttp3:mockwebserver:3.10.0'

    androidTestUtil 'androidx.test:orchestrator:1.1.0'
}

3 个答案:

答案 0 :(得分:1)

您需要向测试中的APK添加“片段测试”依赖性,而不是测试APK。

因此,请将您的build.gradle更新为

debugImplementation "androidx.fragment:fragment-testing:$fragment_version"

来自

androidTestImplementation "androidx.fragment:fragment-testing:$fragment_version"

(这是由于FragmentScenario的实现细节所致。“ fragment-testing”声明了Activity并由FragmentScenario使用。在测试APK中声明的Activity与受测试的APK在不同的进程中运行。为了在同一进程中执行Fragment的代码,则需要将“片段测试”库放入您的APK中,而不是在测试APK中。)

这里也是开发者网站中的tutorial page

答案 1 :(得分:0)

I believe there is a limitation within the launch(Intent startActivityIntent) method of ActivityScenario. It only wants the Activity to be RESUMED or DESTROYED and if it isn't within 4.5 seconds then it throws that error.

Within public static <A extends Activity> ActivityScenario<A> launch(Intent startActivityIntent) of Activity Scenario, check the logic scenario.waitForActivityToBecomeAnyOf(State.RESUMED, State.DESTROYED);

If you can create your own custom Activity Scenario and adjust this line of code to be something like scenario.waitForActivityToBecomeAnyOf(State.ON_PRE_CREATE, State.DESTROYED); then it will theoretically work for you.

This is happening because the Lifecycle.State of your Activity when launching the Fragment is not either of the two specific lifecycle states specified by ActivityScenario. Again, I believe this may be a limitation of the API and I am filing an issue under Android-Test Repo. https://github.com/android/android-test/issues/new

答案 2 :(得分:0)

在我的情况下,由于

,无法打开片段所在的活动EmptyFragmentScenario。
Caused by: java.lang.ClassNotFoundException: Didn't find class androidx.fragment.app.testing.FragmentScenario$EmptyFragmentActivity

查看完整的堆栈跟踪:

2018-12-12 02:12:46.529 32659-32659/? E/AndroidRuntime: FATAL EXCEPTION: main
    Process: app.debug.test, PID: 32659
    java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{app.debug.test/androidx.fragment.app.testing.FragmentScenario$EmptyFragmentActivity}: java.lang.ClassNotFoundException: Didn't find class "androidx.fragment.app.testing.FragmentScenario$EmptyFragmentActivity" on path: DexPathList[[zip file "/system/framework/android.test.mock.jar", zip file "/system/framework/android.test.runner.jar", zip file "/data/app/app.debug.test-HdSyMEsvYzlt1aceQIeIuw==/base.apk"],nativeLibraryDirectories=[/data/app/app.test-HdSyMEsvYzlt1aceQIeIuw==/lib/arm64, /system/lib64, /vendor/lib64]]
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2843)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6669)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
     Caused by: java.lang.ClassNotFoundException: Didn't find class "androidx.fragment.app.testing.FragmentScenario$EmptyFragmentActivity" on path: DexPathList[[zip file "/system/framework/android.test.mock.jar", zip file "/system/framework/android.test.runner.jar", zip file "/data/app/app.debug.test-HdSyMEsvYzlt1aceQIeIuw==/base.apk"],nativeLibraryDirectories=[/data/app/app.debug.test-HdSyMEsvYzlt1aceQIeIuw==/lib/arm64, /system/lib64, /vendor/lib64]]
        at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:134)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
        at android.app.AppComponentFactory.instantiateActivity(AppComponentFactory.java:69)
        at android.app.Instrumentation.newActivity(Instrumentation.java:1215)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2831)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048) 
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78) 
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108) 
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808) 
        at android.os.Handler.dispatchMessage(Handler.java:106) 
        at android.os.Looper.loop(Looper.java:193) 
        at android.app.ActivityThread.main(ActivityThread.java:6669) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858) 
        Suppressed: java.lang.NoClassDefFoundError: Failed resolution of: Landroidx/fragment/app/FragmentActivity;
        at java.lang.VMClassLoader.findLoadedClass(Native Method)
        at java.lang.ClassLoader.findLoadedClass(ClassLoader.java:738)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:363)
                ... 15 more
     Caused by: java.lang.ClassNotFoundException: Didn't find class "androidx.fragment.app.FragmentActivity" on path: DexPathList[[zip file "/system/framework/android.test.mock.jar", zip file "/system/framework/android.test.runner.jar", zip file "/data/app/app.debug.test-HdSyMEsvYzlt1aceQIeIuw==/base.apk"],nativeLibraryDirectories=[/data/app/app.debug.test-HdSyMEsvYzlt1aceQIeIuw==/lib/arm64, /system/lib64, /vendor/lib64]]
        at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:134)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
                ... 18 more

我找不到正确的依赖关系,以便在运行时包含EmptyFragmentActivity,因此我的临时解决方法是不使用launchFragmentInContainer而是启动自己的Activity:

我的测试活动:

class TestFragmentActivity : AppCompatActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
  }

  fun replaceFragment(fragment: Fragment) {
    supportFragmentManager
        .beginTransaction()
        .replace(android.R.id.content, fragment)
        .commit()
  }
}

我的测试:

@RunWith(AndroidJUnit4::class)
class MyFragmentAndroidTest {

  @get:Rule val activityRule: ActivityTestRule<TestFragmentActivity> =
      ActivityTestRule(TestFragmentActivity::class.java)

  @Test
  fun test() {
    activityRule.activity.replaceFragment(MyFragment.newInstance())

    onView(withId(R.id.title)).check(matches(withText("Title"))))
    // More assertions.
  }
}