Загрузка изображений активов в AndroidTest не удается с помощью Picasso / Glide - PullRequest
1 голос
/ 01 февраля 2020

Короткий вопрос

Как я могу успешно загрузить изображение актива в AndroidTest с Picasso или Glide?

Длинное объяснение

В тесте пользовательского интерфейса я хочу вызвать мое приложение для загрузки изображений в ImageView с применением к ним определенных операций (например, обрезка, центрирование и т. Д. c.). Для загрузки изображения, обрезки и т. Д. c. Я использую Picasso. В тесте пользовательского интерфейса я хочу убедиться, что после запуска загрузки через пользовательский интерфейс растровое изображение, прикрепленное к ImageView, является именно тем, что я намеревался использовать с помощью Picasso, т.е. я подключаю Picasso и применяю Picasso DSL правильно.

Для выполнения что я раскрутил андроид-тест, предоставив изображения для проверки в качестве активов в src/androidTest/resources/assets. Я вставляю file:///android_asset/$filename в качестве имени файла изображения в свое приложение, чтобы Picasso использовал AssetManager для загрузки изображения.

Хотя приложение работает с обычными изображениями, сохраненными на устройстве / в эмуляторе, тест не загружается изображения активов - AssetManager сообщает об исключении FileNotFoundException:

     Caused by: java.io.FileNotFoundException: australia.jpg
        at android.content.res.AssetManager.nativeOpenAsset(Native Method)
        at android.content.res.AssetManager.open(AssetManager.java:744)
        at android.content.res.AssetManager.open(AssetManager.java:721)
        at com.squareup.picasso.AssetRequestHandler.load(AssetRequestHandler.java:45)
        at com.squareup.picasso.BitmapHunter.hunt(BitmapHunter.java:206)
        at com.squareup.picasso.RequestCreator.get(RequestCreator.java:396)

Кстати, использование Glide приводит к тому же исключению FileNotFoundException.

Я настроил небольшой инструментальный тест AssetTest.kt для анализа и демонстрации проблемы. Пожалуйста, перейдите по ссылке, чтобы полностью просмотреть его на GitHub.

@RunWith(AndroidJUnit4::class)
@LargeTest
class AssetTest {

    private lateinit var activityScenario: ActivityScenario<TasksActivity>
    private val filename = "victoria-priessnitz-KBIui3I44SY-unsplash.jpg"
    // NOTE: the tests show the same behavior regardless of whether the activity is launched or not
    private val enableActivityUsage = true

    @Before
    fun launchActivity() {
        if (enableActivityUsage) {
            activityScenario = ActivityScenario.launch(TasksActivity::class.java)
        }
    }

    @After
    fun shutdownActivity() {
        if (enableActivityUsage) {
            activityScenario.close()
        }
    }

    @Test
    fun loadingAssetImageWithInstrumentationContextSucceeds() {
        val ctx = InstrumentationRegistry.getInstrumentation().context
        val inputStream = ctx.resources.assets.open(filename)
        val bytes = inputStream.readBytes()
        assertEquals(1903567, bytes.size)
    }

    @Test(expected = FileNotFoundException::class)
    fun loadingAssetImageWithInstrumentationTargetContextFails() {
        val targetCtx = InstrumentationRegistry.getInstrumentation().targetContext
        val inputStream = targetCtx.resources.assets.open(filename)
        inputStream.readBytes()
    }

    @Test(expected = FileNotFoundException::class)
    fun loadingAssetImageWithInstrumentationTargetContextsApplicationContextFails() {
        val targetCtx = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext
        val inputStream = targetCtx.resources.assets.open(filename)
        inputStream.readBytes()
    }

    // NOTE: when not using the test orchestrator to separate tests, this tests may fail
    // because the execution shows the same behavior as with targetContext
    @Test(expected = NullPointerException::class)
    fun picassoFailsWithNullPointerExceptionWhenUsingContext() {
        val uri = Uri.parse("file:///android_asset/$filename")
        // synchronous image loading cannot be performed in main thread
        val context = InstrumentationRegistry.getInstrumentation().context
        try {
            Picasso.with(context)
                    .load(uri)
                    .get()
        } catch (e: Exception) {
            // sometimes Android Studio does not display callstacks in debugger
            println(e.message)
            throw e
        }
    }

    @Test(expected = FileNotFoundException::class)
    fun picassoFailsWithExceptionInAssetManagerOpenWhenUsingTargetContext() {
        val uri = Uri.parse("file:///android_asset/$filename")
        val context = InstrumentationRegistry.getInstrumentation().targetContext
        try {
            Picasso.with(context)
                    .load(uri)
                    .get()
        } catch (e: Exception) {
            // sometimes Android Studio does not display callstacks in debugger
            println(e.message)
            throw e
        }
    }
}

Я заметил следующее:

  1. Тест loadingAssetImageWithInstrumentationContextSucceeds может загружать актив в то время как и loadingAssetImageWithInstrumentationTargetContextFails не может загрузить актив - независимо от того, запущен он в ActivityScenario или нет. Я пришел к выводу, что ресурсы хранятся в APK, доступном из контекста, но не в APK, доступном из targetContext. К сожалению, я не знаю способа заглянуть внутрь APK, на который есть ссылки, так как Device File Explorer не может открыть их в /data/app.

  2. Тест picassoFailsWithNullPointerExceptionWhenUsingContext показывает, что использование InstrumentationRegistry.getInstrumentation().context позволяет Ошибка Пикассо из-за исключения нулевого указателя. Я мог видеть во внутренних органах построителя Picasso, что Picasso напрямую не использует предоставленный контекст, а вызывает ContextImpl::getApplicationContext - где mPackageInfo.getApplication равно null:

    @Override
    public Context getApplicationContext() {
        return (mPackageInfo != null) ?
                mPackageInfo.getApplication() : mMainThread.getApplication();
    }

Тест picassoFailsWithExceptionInAssetManagerOpenWhenUsingTargetContext показывает, что использование InstrumentationRegistry.getInstrumentation().targetContext не вызовет исключение нулевого указателя, но приведет к FileNotFoundException из AssetManager, как упоминалось выше. Я прихожу к выводу, что присоединенный APK-файл targetContext.applicationContext не содержит ресурсов.

Я довольно озадачен всеми существующими контекстами, создаваемыми при ускорении операций.

Но возвращаясь к вопросу:

Конечно, есть и другие (более дорогие) способы тестирования моего сценария, но я думаю, что должен быть рабочий способ просто загрузить актив с Пикассо в инструментальном тесте.

...