关于android:将Junit4测试迁移到androidx:是什么原因导致“无法加载委托人”?

Migrating Junit4 tests to androidx: What causes 'delegate runner could not be loaded'?

我正在将应用程序迁移到androidx,但似乎无法使单元测试正常工作。 我以Google的AndroidJunitRunnerSample为例,该示例已更新为使用新的androidx api。 尝试运行测试时出现以下错误:

1
java.lang.Exception: Delegate runner 'androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner' for AndroidJUnit4 could not be loaded. Check your build configuration.

这是我的模块build.gradle:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
android {
    defaultConfig {
        testInstrumentationRunner"androidx.test.runner.AndroidJUnitRunner"
    }
}
dependencies {
    // Test dependencies
    androidTestImplementation 'androidx.test:core:1.0.0-beta02'
    androidTestImplementation 'androidx.test.ext:junit:1.0.0-beta02'
    androidTestImplementation 'androidx.test:runner:1.1.0-beta02'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0-beta02'
    androidTestImplementation"androidx.arch.core:core-testing:2.0.0"
    androidTestImplementation 'androidx.room:room-testing:2.1.0-alpha01'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0-beta02'
    androidTestImplementation 'org.hamcrest:hamcrest-library:1.3'
}

这是我的测试的结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;

import androidx.test.ext.junit.runners.AndroidJUnit4;

@RunWith(AndroidJUnit4.class)
public class EntityParcelTest {

    @BeforeClass
    public void createEntities() {
        // Setup...
    }

    @Test
    void someTest() {
        // Testing here
    }

我究竟做错了什么?


从测试类中删除@RunWith(AndroidJUnit4.class)批注解决了该问题,尽管我不能真正说出为什么或如何解决。

编辑:好吧,我做了一些更多的测试。我将应用程序迁移到Kotlin,突然间,我注意到测试也开始在@RunWith注释中起作用。这是我发现的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;

import androidx.test.ext.junit.runners.AndroidJUnit4;

@RunWith(AndroidJUnit4.class) // <-- @RunWith + @BeforeClass = Error
public class AndroidXJunitTestJava {

    @BeforeClass
    public static void setup() {
        // Setting up once before all tests
    }

    @Test
    public void testing() {
        // Testing....
    }
}

此Java测试失败,并出现Delegate runner for AndroidJunit4 could not be loaded错误。但是,如果删除@RunWith批注,它将起作用。另外,如果我仅用@Before替换@BeforeClass设置,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import androidx.test.ext.junit.runners.AndroidJUnit4;

@RunWith(AndroidJUnit4.class) // <-- @RunWith + @Before = works?
public class AndroidXJunitTestJava {

    @Before
    public void setup() {
        // Setting up before every test
    }

    @Test
    public void testing() {
        // Testing....
    }
}

测试将无错误运行。我需要使用@BeforeClass批注,因此我刚刚删除了@RunWith

但是现在我正在使用Kotlin,以下方法(应该等于第一个Java示例)起作用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class AndroidXJunitTest {

    companion object {
        @BeforeClass fun setup() {
            // Setting up
        }
    }

    @Test
    fun testing() {
        // Testing...
    }

}

同样,正如亚历山德罗·比埃塞克(Alessandro Biessek)在回答中说,@ Ioane Sharvadze在评论中说的那样,@Rule注释可能发生相同的错误。如果我添加一行

1
 @Rule val instantTaskExecutorRule = InstantTaskExecutorRule()

对于Kotlin示例,发生相同的委托运行器错误。这必须替换为

1
@get:Rule val instantTaskExecutorRule = InstantTaskExecutorRule()

在这里解释。


如果您在Kotlin中使用测试规则并将其编写为Java样式,也会收到错误消息

1
2
@Rule
var mainActivityActivityTestRule = ActivityTestRule(MainActivity::class.java)

您必须将@Rule更改为@get:Rule

1
2
@get:Rule
var mainActivityActivityTestRule = ActivityTestRule(MainActivity::class.java)


删除@Test方法时显示的错误消息。

您可以尝试放置@Test方法并运行

1
2
3
4
5
@Test
public void signInTest() {


}


在测试活动时,请确保ActivityTestRule变量是public:

1
2
@Rule
public ActivityTestRule<YourActivity> activityTestRule = new ActivityTestRule<>(YourActivity.class);

有两种方法可以尝试解决此问题:

解决方案1:

构建->清洁项目。然后构建->重建项目

解决方案2:

有2个具有AndroidJUnit4的软件包

  • androidx.test.runner(已弃用)
  • androidx.test.ext.junit.runners
  • 确保您的build.gradle(应用程序)具有此行

    1
    2
    3
    4
    5
    6
    7
    android {
        defaultConfig {
        ....
            testInstrumentationRunner"androidx.test.runner.AndroidJUnitRunner"
        ....
        }
    }

    并确保在测试类之前,在@RunWith(AndroidJUnit4.class)中使用(1)(已弃用)而不是(2)(新的但尚未稳定)。


    改变中

    1
    2
    3
    4
    @Test
    void someTest() {
        // Testing here
    }

    1
    2
    3
    4
    @Test
    public void someTest() {
        // Testing here
    }

    为我工作。


    测试框架实际上提供的信息太少。我遇到了相同的问题,并进入了堆栈跟踪,并发现了以下验证:

    enter image description here

    因此,您必须声明您的@BeforeClass方法static


    您需要确保所有带有@BeforeClass批注的类都是public static。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import org.junit.BeforeClass;
    import org.junit.Test;
    import org.junit.runner.RunWith;

    import androidx.test.ext.junit.runners.AndroidJUnit4;

    @RunWith(AndroidJUnit4.class)
    public class EntityParcelTest {

        @BeforeClass
        public static void createEntities() {
            // Setup...
        }

        @Test
        public void someTest() {
            // Testing here
        }

    您可以使用@JvmField。来自文档

    Instructs the Kotlin compiler not to generate getters/setters for this property and expose it as a field

    1
    2
    3
    @Rule
    @JvmField
    val activityActivityTestRule = ActivityScenarioRule<MainActivity>(MainActivity::class.java)

    如果在添加以下依赖项之后:

    1
    2
    3
    4
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation
               'com.android.support.test.espresso:espresso-core:3.0.1'

    1
    2
    3
    4
    5
    6
    7
    android{
    defaultConfig{
        ...
        testInstrumentationRunner"androidx.test.runner.AndroidJUnitRunner"
        ...
       }
    }

    则不能使用属于androidx.test.runner包的已弃用的AndroidJUnit4类,而不是使用属于androidx.test.ext.junit.runners.AndroidJUnit4包的类。

    我仍然不明白为什么不推荐使用AndroidJUnit4类,而Android团队却在各处都建议使用它所属的Gradle依赖项,即Codelabs和docs


    其他一些可能的原因:

    • 仅具有@SmallTest(或中/大)测试方法。用@Test标记至少一种方法可以解决此问题。
    • setup() / teardown()方法不是公开的。

    对我来说,问题是@Rule注释。
    我现在不知道原因。

    作为一种临时解决方法,您可以例如使用UIAutomator for ActivityTestRule开始您的活动。


    也许您尚未在gradle配置文件上更新跑步者?

    1
    2
    3
    4
    defaultConfig {
        ...
        testInstrumentationRunner"androidx.test.runner.AndroidJUnitRunner"
    }

    另外,AndroidStudio 3.2还提供了一个选项,可以自动将依赖项迁移到AndroidX(重构->迁移到AndroidX ...),这对我来说是有效的。


    我修复了此配置:

    我的依赖:

    1
    2
    3
    4
    5
    6
    /*Instrumentation Test*/
    androidTestImplementation"org.assertj:assertj-core:3.12.2"
    androidTestImplementation ('androidx.test.espresso:espresso-core:3.2.0',{
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    androidTestImplementation"androidx.arch.core:core-testing:2.1.0-rc01"

    在我的defaultConfig部分中,我有:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    defaultConfig {
        applicationId"uy.edu.ude.archcomponents"
        minSdkVersion 22
        targetSdkVersion 28
        versionCode 1
        versionName"1.0"
        testInstrumentationRunner"androidx.test.runner.AndroidJUnitRunner"

        testInstrumentationRunnerArguments clearPackageData: 'true'
    }

    在测试中,我有:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    package uy.edu.ude.archcomponents.repository

    import androidx.arch.core.executor.testing.InstantTaskExecutorRule
    import androidx.room.Room
    import androidx.test.platform.app.InstrumentationRegistry
    import androidx.test.runner.AndroidJUnit4
    import com.example.android.roomwordssample.WordDao
    import com.example.android.roomwordssample.WordRoomDatabase
    import kotlinx.coroutines.runBlocking
    import org.assertj.core.api.Assertions.assertThat
    import org.junit.After
    import org.junit.Before
    import org.junit.Rule
    import org.junit.Test
    import org.junit.runner.RunWith
    import uy.edu.ude.archcomponents.entity.Word
    import java.io.IOException


    @RunWith(AndroidJUnit4::class)
    class WordDaoTest {

        @get:Rule
        val instantTaskExecutorRule = InstantTaskExecutorRule()

        private lateinit var wordDao: WordDao
        private lateinit var db: WordRoomDatabase

        @Before
        fun createDb() {
            val context = InstrumentationRegistry.getInstrumentation().context
            // Using an in-memory database because the information stored here disappears when the
            // process is killed.
            db = Room.inMemoryDatabaseBuilder(context, WordRoomDatabase::class.java)
                    // Allowing main thread queries, just for testing.
                    .allowMainThreadQueries()
                    .build()
            wordDao = db.wordDao()
        }

        @After
        @Throws(IOException::class)
        fun closeDb() {
            db.close()
        }

        @Test
        @Throws(Exception::class)
        fun insertAndGetWord() {
            runBlocking {
                val word = Word("word")
                wordDao.insert(word)
                val allWords = wordDao.getAlphabetizedWords().waitForValue()
                assertThat(allWords[0].word).isEqualTo(word.word)
            }
        }
    }

    我也使用android插件版本3.4.2。我从这里获取配置


    就我而言,我跳过了用@Test注释测试用例的情况。这不是你的情况。这个答案是给像我这样的人的。


    就我而言,定义函数参数是问题所在。

    1
    2
    3
    4
    @Test
    fun foo(string: String ="") {  // Causes"Delegate runner could not be loaded" error.
        // ...
    }

    我必须执行以下操作。

    1
    2
    3
    4
    5
    6
    7
    8
    @Test
    fun foo() {
        foo("")
    }

    fun foo(string: String) {  // Can be called from other test functions.
        // ...
    }

    就我而言,我通过将所有@Test@Before@After方法显式声明为public,并将@BeforeClass@AfterClass声明为public static来解决此问题。

    如果您将这些方法中的任何一个都隐式/显式地protected或显式private保留;您将遇到以下异常

    1
    java.lang.RuntimeException: Delegate runner 'androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner' for AndroidJUnit4 could not be loaded.

    因此,正确的方法是使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Before
    public void setUp() {

    }

    @BeforeClass
    public static void init() {

    }

    代替:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    @Before
    void setUp() {

    }

    @Before
    protected void setUp() {

    }

    @Before
    private void setUp() {

    }

    @BeforeClass
    public void init() {

    }

    在类和超类中,带有@Test批注的函数中的AndroidJUnit4都不支持:

    • 参数:@Test fun dontWork(param: String) {}
    • 私人访问修饰符:@Test private fun dontWork2() {}

    注意:如果没有@Test批注,则允许以上内容

    预期的方式可能是:

    ClassTest.kt

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import androidx.test.ext.junit.runners.AndroidJUnit4
    import org.junit.Test
    import org.junit.runner.RunWith

    @RunWith(AndroidJUnit4::class)
    class ClassTest : SuperTest() {

        @Test
        fun test() {}

        private fun allowedWithoutAnnotation(paramAllowed: String) {}

    }

    build.gradle(:mobile)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    defaultConfig {
        ...
        testInstrumentationRunner"androidx.test.runner.AndroidJUnitRunner"
    }


    dependencies {
        ...
        testImplementation 'junit:junit:4.12'
        androidTestImplementation 'androidx.test.ext:junit:1.1.1'
        androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
        androidTestImplementation 'androidx.arch.core:core-testing:2.1.0'
    }

    GL


    我将这个错误归结为只是将乐趣设置为私有,删除此错误对我来说解决了。