Перенос тестов Junit4 на androidx: в чем причина того, что «бегун делегата не может быть загружен»?

Я переношу свое приложение на androidx, мне кажется, что мои модульные тесты не работают. Я взял пример из Google AndroidJunitRunnerSample, который был обновлен для использования нового api androidx. При попытке запустить тесты я получаю следующую ошибку:

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
    }

Что я делаю неправильно?

Пожалуйста, обратитесь к сообщению ниже stackoverflow.com/questions/55496047/…

Tarun A 23.10.2020 06:21
58
1
26 894
20
Перейти к ответу Данный вопрос помечен как решенный

Ответы 20

Может быть, вы не обновили раннер в конфигурационном файле gradle?

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

Кроме того, в AndroidStudio 3.2 есть возможность автоматизировать миграцию ваших зависимостей в AndroidX (Refactor -> Migrate to AndroidX ...), которая сделала это за меня.

Кажется, у меня уже есть этот testInstrumentationRunner, установленный в моем build.gradle, и я также выполнил рефакторинг «Migrate to AndroidX».

J. Nuutinen 19.10.2018 18:57
Ответ принят как подходящий

Удаление аннотаций @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 происходит та же ошибка исполнителя делегата. Это необходимо заменить на

@get:Rule val instantTaskExecutorRule = InstantTaskExecutorRule()

Пояснение здесь.

Но теперь тест - это тест JUnit.

Ioane Sharvadze 22.10.2018 11:25

Для меня проблема заключалась в том, что я использую @Rule вместо аннотации @get:Rule для ActivityTestRule. Если вы используете Kotlin, это может вам помочь.

Ioane Sharvadze 22.10.2018 11:43

В Kotlin для меня было достаточно замены @Rule на @get:Rule. Работает с @Before.

Miloš Černilovský 13.12.2018 16:51

Для меня решением было изменить метод, помеченный @BeforeClass, на метод static согласно stackoverflow.com/a/733042/2848676. Похоже, это тоже помогло решить исходную проблему.

Michael Osofsky 08.03.2019 01:00

Замена @Rule на @get:Rule у меня сработала в котлине. Спасибо :)

Mohit Atray 16.02.2020 20:16

Спасибо, удаление @RunWith (AndroidJUnit4.class) сработало. Обычно мне нравятся объяснения, но, кажется, я их не нахожу. Согласно документации, аннотация обязательна и должна работать developer.android.com/training/testing/junit-runner Я предполагаю здесь, но это просто кажется несовместимостью некоторой библиотеки / класса. Надеюсь, кто-то, у кого есть время, сможет разобраться, я сделаю это, если найду. Но пока это решение работает, так что голосуйте.

MG Developer 11.10.2020 03:05

Для меня проблема заключалась в аннотации @Rule..
Я пока не знаю причины.

В качестве временного обходного пути вы можете, например, начать свою деятельность с помощью UIAutomator для ActivityTestRule.

Вы можете использовать @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) 

В чем разница?

IgorGanapolsky 12.03.2019 17:02

с @get: вы добавляете эту аннотацию к подчиненному java-получателю, созданному для этого поля (который является методом) вместо самого поля

Filipkowicz 10.04.2019 14:59

Хорошая точка зрения. Я предпочитаю использовать @JvmField вместо префикса аннотации @get:, поскольку он генерирует тот же байт-код, что и Java с первоначально упомянутым синтаксисом. Я не скажу, что это личное предпочтение (поскольку он делает разные вещи), но до сих пор он был защищен от ошибок в сотнях тестов, и я предпочитаю его, так как могу проверить, что он работает.

milosmns 26.09.2019 23:39

Если после добавления следующих зависимостей:

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

не помогает тогда использовать устаревший класс AndroidJUnit4, который принадлежит пакету androidx.test.runner, вместо класса, принадлежащего пакету androidx.test.ext.junit.runners.AndroidJUnit4.

Я до сих пор не понимаю, почему класс AndroidJUnit4 устарел, в то время как зависимости Gradle, к которым он принадлежит, везде предлагаются командой Android, то есть Codelabs и документы

Сообщение об ошибке отображается, когда я удаляю метод @Test.

Можно попробовать поставить метод @Test и запустить

@Test
public void signInTest() {


}

Как ни странно, это было для меня. Как только я добавил пустую функцию с пометкой @Test, все было хорошо

Ivan Wooll 10.10.2019 10:25

Изменение

@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. Конфиг взял от здесь

Некоторые другие возможные причины:

  • Имея только методы тестирования @SmallTest (или средние / большие). Маркировка хотя бы одного метода с помощью @Test решает проблемы.
  • setup() / teardown() методы не являются общедоступными.

Фреймворк тестирования на самом деле дает слишком мало информации. Я столкнулся с той же проблемой и покопался в трассировке стека и нашел эти проверки:

Итак, вы должны объявить свой @BeforeClass метод static.

Есть два способа решить эту проблему:

Решение 1:

Сборка -> Чистый проект. Затем Build -> Rebuild Project

Решение 2:

Есть 2 пакета с AndroidJUnit4

  1. androidx.test.runner (не рекомендуется)
  2. androidx.test.ext.junit.runners

Убедитесь, что в вашем build.gradle (приложении) есть эта строка

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

и убедитесь, что вы используете (1) (который устарел) вместо (2) (новый, но еще не стабильный) в @RunWith(AndroidJUnit4.class) перед тестовым классом.

У меня ушло около 2 часов, чтобы наконец настроить один-единственный AndroidJUnitTest, хотя и следуя официальным документам. Как указано в этом ответе в моем тестовом классе, я изменил импорт: USE: import androidx.test.runner.AndroidJUnit4 (хотя и не рекомендуется) НЕ ИСПОЛЬЗУЙТЕ: import androidx.test.ext.junit.runners.AndroidJUnit4. Не идеальное решение. Но лучше использовать устаревший класс вместо того, чтобы тратить больше времени на запуск одного теста. Спасибо за Ваш ответ.

user2350644 03.07.2020 12:45

Что касается моего случая, проблемой было определение параметра функции.

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

Во время тестирования активности убедитесь, что переменная ActivityTestRule является общедоступной:

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

В моем случае я исправил это, явно объявив все методы @Test, @Before и @After как public и объявив @BeforeClass и @AfterClass как public static.

Если вы оставите для любого из этих методов неявно / явно 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 ни в классе, ни в суперклассе:

  • параметры: @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, является общедоступный статический. Например:

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, я обнаружил, что проблема заключается в переменной composeTestRule, которая была частной, поэтому Убедитесь, что ваша переменная composeTest не должна быть частной.

@get: Правило val composeTestRule = createAndroidComposeRule ()

НЕ КАК

@get: Правило частный val composeTestRule = createAndroidComposeRule ()

Другие вопросы по теме