Короткий вопрос
Как я могу успешно загрузить изображение актива в 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
}
}
}
Я заметил следующее:
Тест loadingAssetImageWithInstrumentationContextSucceeds
может загружать актив в то время как и loadingAssetImageWithInstrumentationTargetContextFails
не может загрузить актив - независимо от того, запущен он в ActivityScenario или нет. Я пришел к выводу, что ресурсы хранятся в APK, доступном из контекста, но не в APK, доступном из targetContext. К сожалению, я не знаю способа заглянуть внутрь APK, на который есть ссылки, так как Device File Explorer не может открыть их в /data/app.
Тест 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
не содержит ресурсов.
Я довольно озадачен всеми существующими контекстами, создаваемыми при ускорении операций.
Но возвращаясь к вопросу:
Конечно, есть и другие (более дорогие) способы тестирования моего сценария, но я думаю, что должен быть рабочий способ просто загрузить актив с Пикассо в инструментальном тесте.