Android仪器化单元测试中的上下文和资源

5
我正在设计一个系统,其中包含一些不太简单的类,需要一个上下文对象来初始化。这些类使用第三方类,这些类也需要上下文初始化。这个类还利用上下文加载了许多功能所必需的字符串资源。
问题在于编写这些类的仪器化单元测试时。当我尝试使用InstrumentationRegistry.getContext()获取测试的上下文对象时,会遇到一个异常,即上下文无法找到与类相关联的字符串资源(android.content.res.Resources$NotFoundException)。
我的问题是:如何设计这些测试,以便上下文可以检索我需要的字符串资源,并作为第三方类的合适上下文对象?由于某些类处理身份验证令牌,因此只能进行有限的模拟。我不可能是在Android领域遇到这个问题的唯一人,所以我相信对于这个普遍的问题,肯定有一个共同的解决方案。
编辑: 如建议所述,我尝试将Robolectric(版本3.3.2)集成到我的项目中,但当我尝试运行我的单元测试时,遇到以下错误:
Error:Error converting bytecode to dex:
Cause: Dex cannot parse version 52 byte code.
This is caused by library dependencies that have been compiled using Java 8 or above.
If you are using the 'java' gradle plugin in a library submodule add 
targetCompatibility = '1.7'
sourceCompatibility = '1.7'
to that submodule's build.gradle file. 

我尝试在我的gradle文件中添加targetCompatibility和sourceCompatibility行,但没有成功。

这是我的mobile build.gradle文件:

apply plugin: 'com.android.application'
apply plugin: 'checkstyle'
apply plugin: 'io.fabric'

project.ext {
    supportLibVersion = '25.3.0'
    multiDexSupportVersion = '1.0.1'
    gsonVersion = '2.8.0'
    retrofitVersion = '2.2.0'
    daggerVersion = '2.4'
    butterKnifeVersion = '8.5.1'
    eventBusVersion = '3.0.0'
    awsCoreServicesVersion = '2.2.+'
    twitterKitVersion = '2.3.2@aar'
    facebookVersion = '4.+'
    crashlyticsVersion = '2.6.7@aar'
    autoValueVersion = '1.2'
    autoValueParcelVersion = '0.2.5'
    autoValueGsonVersion = '0.4.4'
    permissionDispatcher = '2.2.0'
    testRunnerVersion = '0.5'
    espressoVersion = '2.2.2'
    junitVersion = '4.12'
    roboelectricVersion = '3.3.2'
}

def gitSha = exec('git rev-parse --short HEAD', "unknown");
def gitCommitCount = 100 + Integer.parseInt(exec('git rev-list --count HEAD', "-1"))
def gitTag = exec('git describe --tags', stringify(gitCommitCount))
def gitTimestamp = exec('git log -n 1 --format=%at', -1)

def appId = "com.example.myapp"
def isCi = "true".equals(System.getenv("CI"))

// Uncomment if you wish to enable Jack & Java8
// apply from: 'jack.gradle'

// Uncomment if you wish to enable Sonar
//apply from: 'sonar.gradle'

android {
  compileSdkVersion 25
  buildToolsVersion "25.0.2"

  defaultConfig {
    applicationId appId
    minSdkVersion 16
    targetSdkVersion 25

      multiDexEnabled = true

    versionCode gitCommitCount
    versionName gitTag

    testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

    buildConfigField 'String', 'GIT_SHA', "\"${gitSha}\""
    buildConfigField 'long', 'GIT_TIMESTAMP', "${gitTimestamp}L"
  }

  buildTypes {
    debug {
      applicationIdSuffix '.debug'
    }
    release {
      minifyEnabled true
      proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
    qa.initWith(buildTypes.release)
    qa {
      applicationIdSuffix '.qa'
      debuggable true
    }
  }

  lintOptions {
    abortOnError false
  }

  applicationVariants.all { variant ->
    def strictMode = !variant.name.equals("release")
    buildConfigField 'boolean', 'STRICT_MODE_ENABLED', "${strictMode}"
  }
}

configurations.all {
  resolutionStrategy {
    force "com.android.support:support-annotations:$supportLibVersion"
    force "com.squareup.okhttp3:okhttp:3.4.1"
    force "com.squareup:okio:1.9.0"
    force "com.google.guava:guava:19.0"
  }
}

dependencies {
    compile "com.android.support:appcompat-v7:$supportLibVersion"
    compile "com.android.support:design:$supportLibVersion"
    compile "com.android.support:recyclerview-v7:$supportLibVersion"
    compile "com.android.support:cardview-v7:$supportLibVersion"
    compile "com.android.support:multidex:$multiDexSupportVersion"

    compile "com.squareup.retrofit2:retrofit:$retrofitVersion"
    compile "com.squareup.retrofit2:converter-gson:$retrofitVersion"
    compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'

    compile "com.google.dagger:dagger:$daggerVersion"
    annotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion"
    provided 'javax.annotation:jsr250-api:1.0'

    compile 'com.github.bumptech.glide:glide:3.7.0'
    compile 'com.jakewharton.timber:timber:4.3.1'
    compile "com.jakewharton:butterknife:$butterKnifeVersion"
    annotationProcessor "com.jakewharton:butterknife-compiler:$butterKnifeVersion"

    compile "org.greenrobot:eventbus:$eventBusVersion"
    annotationProcessor "org.greenrobot:eventbus:$eventBusVersion"

    compile 'io.reactivex.rxjava2:rxandroid:2.0.0'
    debugCompile 'com.squareup.okhttp3:logging-interceptor:3.4.2'

    compile "com.google.auto.value:auto-value:$autoValueVersion"
    annotationProcessor "com.google.auto.value:auto-value:$autoValueVersion"

    compile "com.ryanharter.auto.value:auto-value-parcel-adapter:$autoValueParcelVersion"
    annotationProcessor "com.ryanharter.auto.value:auto-value-parcel:$autoValueParcelVersion"

    compile "com.github.hotchemi:permissionsdispatcher:$permissionDispatcher"
    annotationProcessor "com.github.hotchemi:permissionsdispatcher-processor:$permissionDispatcher"

    compile("com.crashlytics.sdk.android:crashlytics:$crashlyticsVersion") {
        transitive = true;
    }

    compile("com.twitter.sdk.android:twitter:$twitterKitVersion") {
        transitive = true
    }

    compile "com.facebook.android:facebook-android-sdk:$facebookVersion"

    compile "com.amazonaws:aws-android-sdk-core:$awsCoreServicesVersion"
    annotationProcessor "com.amazonaws:aws-android-sdk-core:$awsCoreServicesVersion"
    compile "com.amazonaws:aws-android-sdk-apigateway-core:$awsCoreServicesVersion"
    annotationProcessor "com.amazonaws:aws-android-sdk-apigateway-core:$awsCoreServicesVersion"
    compile "com.amazonaws:aws-android-sdk-cognito:$awsCoreServicesVersion"
    annotationProcessor "com.amazonaws:aws-android-sdk-cognito:$awsCoreServicesVersion"
    compile "com.amazonaws:aws-android-sdk-cognitoidentityprovider:$awsCoreServicesVersion"
    annotationProcessor "com.amazonaws:aws-android-sdk-cognitoidentityprovider:$awsCoreServicesVersion"
    compile "com.amazonaws:aws-android-sdk-lambda:$awsCoreServicesVersion"
    annotationProcessor "com.amazonaws:aws-android-sdk-lambda:$awsCoreServicesVersion"
    compile "com.amazonaws:aws-android-sdk-sns:$awsCoreServicesVersion"
    annotationProcessor "com.amazonaws:aws-android-sdk-sns:$awsCoreServicesVersion"

    androidTestCompile "junit:junit:$junitVersion"
    androidTestCompile "com.android.support.test:runner:$testRunnerVersion"
    androidTestCompile "com.android.support.test:rules:$testRunnerVersion"
    androidTestCompile "com.android.support.test.espresso:espresso-intents:$espressoVersion"
    androidTestCompile "com.android.support.test.espresso:espresso-core:$espressoVersion"
    androidTestCompile "com.squareup.retrofit2:retrofit-mock:$retrofitVersion"
    androidTestCompile "org.robolectric:robolectric:$roboelectricVersion"

    testCompile "junit:junit:$junitVersion"
    testCompile 'com.google.truth:truth:0.30'
    testCompile 'org.hamcrest:hamcrest-all:1.3'
    testCompile "org.robolectric:robolectric:$roboelectricVersion"
}

task checkCodingStyle(type: Checkstyle) {
  description 'Runs Checkstyle inspection against Android sourcesets.'
  group = 'Code Quality'
  ignoreFailures = false
  showViolations = false
  source 'src'
  include '**/*.java'
  exclude '**/gen/**'
  exclude '**/R.java'
  exclude '**/BuildConfig.java'
  reports {
    xml.destination "$project.buildDir/reports/checkstyle/report.xml"
  }
  classpath = files()
  configFile = file("${rootProject.rootDir}/config/checkstyle/checkstyle.xml")
}

def stringify(int versionCode) {
  def builder = new StringBuilder();
  def dot = ""
  String.format("%03d", versionCode).toCharArray().each {
    builder.append(dot)
    builder.append(it)
    dot = "."
  }
  return builder.toString()
}

def exec(String command, Object fallback = null) {
  def cmd = command.execute([], project.rootDir)
  cmd.waitFor()
  if (cmd.exitValue() != 0) {
    if (fallback == null) {
      throw new RuntimeException("'$command' failed: $cmd.errorStream.text")
    } else {
      return fallback
    }
  }
  return cmd.text.trim()
}

if (isCi) {
  build.finalizedBy(checkCodingStyle)
}

太好了!怎么办?我尝试集成Robolectric,但是遇到了Dex解析问题(关于使用Java 8编译库依赖项的问题)。 - John Riley
没问题,我刚刚更新了我的问题。 - John Riley
Robolectric 版本 3.3.2 - John Riley
我的 Robolectric 测试用例在测试文件夹中。我刚刚发布了我的手机 build.gradle。 - John Riley
其实不用了。我从我的build.gradle中删除了与roboelectric相关的androidTestCompile语句,现在一切都正常工作了。感谢您的帮助并迫使我仔细检查我的测试配置! - John Riley
1个回答

10

被接受的答案并不是实际的解决方案。在许多情况下,您希望测试与真正的Android框架的交互。Robolectric和其他存根一样,可能会隐藏一些实际问题。

您的问题在于使用了InstrumentationRegistry.getContext(),它与您的应用程序使用的上下文不同。 根据文档:

返回此检测工具包的上下文。

相反,您应该使用InstrumentationRegistry.getTargetContext()

返回正在检测的目标应用程序的上下文。

因为与第一个上下文不同,它将可以访问您的资源。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接