繁体   English   中英

使用 FragmentScenario 进行仪器测试

[英]Instrumental Testing with FragmentScenario

我正在尝试使用 androidx 测试库的新 FragmentScenario API 进行本地测试和仪器测试 (androidTest)。 该 api 在本地环境中工作正常,但在仪器测试中,它给出错误: java.lang.AssertionError:Activity 永远不会成为请求状态“[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)

仪器测试(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'
}

您需要将“fragment-testing”依赖项添加到被测 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。)

这也是开发人员网站中的教程页面

我相信 ActivityScenario 的launch(Intent startActivityIntent)方法存在局限性。 它只希望 Activity 被 RESUMED 或 DESTROYED,如果它不在 4.5 秒内,那么它会抛出该错误。

public static <A extends Activity> ActivityScenario<A> launch(Intent startActivityIntent)的 Activity Scenario 中,检查逻辑scenario.waitForActivityToBecomeAnyOf(State.RESUMED, State.DESTROYED);

编辑此问题已得到修复。

在我的例子中,启动片段的活动 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.
  }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM