将 Junit4 测试迁移到 androidx:是什么原因导致 'delegate runner could not be loaded'?

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

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

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

这是我的模块 build.gradle:

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'
}

下面是我的测试结构:

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
    }

我做错了什么?

也许您还没有更新 gradle 配置文件中的运行程序?

defaultConfig {
    ...
    testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

此外,AndroidStudio 3.2 有一个选项可以自动将您的依赖项迁移到 AndroidX(重构 -> 迁移到 AndroidX ...),这对我来说是这样的。

从测试中删除 @RunWith(AndroidJUnit4.class) 注释 类 解决了这个问题,虽然我不能真正说出为什么或如何解决它。

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

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 注释,它就会起作用。此外,如果我将 @BeforeClass 设置替换为 @Before,如下所示:

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 示例相同)有效:

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...
    }

}

此外,正如 在回答中和评论中的@Ioane Sharvadze 所说,@Rule 注释可能会发生相同的错误。如果我添加一行

 @Rule val instantTaskExecutorRule = InstantTaskExecutorRule()

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

@get:Rule val instantTaskExecutorRule = InstantTaskExecutorRule()

解释.

对我来说,问题是 @Rule 注释..
我暂时不知道原因。

作为临时解决方法,您可以使用 UIAutomator for ActivityTestRule 启动 activity。

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

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

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

如果您在 Kotlin 中使用测试规则并将其编写为 Java 样式

,您也会收到错误消息
@Rule
var mainActivityActivityTestRule = ActivityTestRule(MainActivity::class.java)

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

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

如果添加以下依赖后:

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'

android{
defaultConfig{
    ...
    testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    ...
   }
}

无济于事然后使用属于 androidx.test.runner 包的已弃用 AndroidJUnit4 class 而不是属于 androidx.test.ext.junit.runners.AndroidJUnit4 包的那个。

我仍然不明白为什么 AndroidJUnit4 class 被弃用,而 Android 团队到处都建议它所属的 Gradle 依赖项,即 Codelabs and docs

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

你可以试试把@Test方法和运行

@Test
public void signInTest() {


}

改变

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

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

适合我。

我发现这个错误是因为简单地将乐趣设置为私有,删除它为我解决了这个问题。

我用这个配置修复了:

我的依赖项:

/*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 部分我有:

defaultConfig {
    applicationId "uy.edu.ude.archcomponents"
    minSdkVersion 22
    targetSdkVersion 28
    versionCode 1
    versionName "1.0"
    testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

    testInstrumentationRunnerArguments clearPackageData: 'true'
}

在测试中我有:

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。我从 here

获取了配置

其他一些可能的原因:

  • 只有@SmallTest(或Medium-/Large-)个测试方法。用 @Test 标记至少一种方法可以解决问题。
  • setup() / teardown() 方法不是 public.

测试框架实际上提供的信息太少了。我遇到了同样的问题并深入研究了堆栈跟踪,发现了这些验证:

所以你必须声明你的@BeforeClass方法static

您可以尝试通过 2 种方法解决此问题:

解决方案 1:

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

解决方案 2:

有 2 个包具有 AndroidJUnit4

  1. androidx.test.runner(已弃用)
  2. androidx.test.ext.junit.runners

确保您的 build.gradle(应用)有此行

android {
    defaultConfig {
    ....
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    ....
    }
}

并确保在测试 class.

之前在 @RunWith(AndroidJUnit4.class) 中使用 (1)(已弃用)而不是 (2)(新但尚未稳定)

至于我的情况,问题在于定义函数参数。

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

我必须执行以下操作。

@Test
fun foo() {
    foo("")
}

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

在测试 activity 时确保 ActivityTestRule 变量是 public :

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

在我的例子中,我通过显式声明所有 @Test@Before@After 方法作为 public 并声明 @BeforeClass@AfterClass 作为 public static

如果您将这些方法中的任何一个保留为 implicitly/explicitly protected,或者明确地 private;你会遇到以下异常

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

所以,正确的做法是使用

@Before
public void setUp() {

}

@BeforeClass
public static void init() {

}

而不是:

@Before
void setUp() {

}

@Before
protected void setUp() {

}

@Before
private void setUp() {

}

@BeforeClass
public void init() {

}

AndroidJUnit4 不支持带有 @Test 注释的函数,既不在 class 也不在 superclass:

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

注意:如果没有 @Test 注释,以上是允许的


预期的方式可能是:

ClassTest.kt

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(:手机)

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

您需要确保任何标有 @BeforeClass 注释的 class 是 public static .例如:

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
    }

在我的案例中,我跳过了使用 @Test 注释测试用例。这不是你的情况。此答案适用于像我这样的其他人。

在 jetpack compose UI 测试中的一个小问题上花了很多时间后,我发现问题出在 composeTestRule 变量中,它是私有的,所以 确保您的 composeTest 变量不应该是私有的。

@get:规则 val composeTestRule = createAndroidComposeRule()

不喜欢

@get:规则 private val composeTestRule = createAndroidComposeRule()

我开始使用 Espresso 时遇到的另一个问题是错误

Failed to instantiate test runner class androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner site:whosebug.com

在这种情况下,我解决了将 Unit 返回到 fun

 fun Example() : Unit {your code}

再见