Я пытаюсь настроить покрытие кода с помощью тестов Android Espresso и запускаю тесты в Firebase Test Lab.
Тесты выполняются на Firebase, поэтому я думаю, что все должно "работать". Но файл cover.ec, сгенерированный в Firebase Test Lab, не содержит никакой информации о покрытии.
Независимо от того, что я пытаюсь, я не вижу, как заставить его генерировать информацию о покрытии.
Просматривая @ журналы тестовой лаборатории Firebase, я вижу эту трассировку стека:
10-02 23:55:37.746: W/System.err(7917): java.io.FileNotFoundException: /jacoco.exec (Read-only file system)
10-02 23:55:37.746: W/System.err(7917): at java.io.FileOutputStream.open0(Native Method)
10-02 23:55:37.746: W/System.err(7917): at java.io.FileOutputStream.open(FileOutputStream.java:287)
10-02 23:55:37.746: W/System.err(7917): at java.io.FileOutputStream.<init>(FileOutputStream.java:223)
10-02 23:55:37.746: W/System.err(7917): at org.jacoco.agent.rt.internal_8ff85ea.output.FileOutput.openFile(FileOutput.java:67)
10-02 23:55:37.746: W/System.err(7917): at org.jacoco.agent.rt.internal_8ff85ea.output.FileOutput.startup(FileOutput.java:49)
10-02 23:55:37.746: W/System.err(7917): at org.jacoco.agent.rt.internal_8ff85ea.Agent.startup(Agent.java:122)
10-02 23:55:37.746: W/System.err(7917): at org.jacoco.agent.rt.internal_8ff85ea.Agent.getInstance(Agent.java:50)
10-02 23:55:37.746: W/System.err(7917): at org.jacoco.agent.rt.internal_8ff85ea.Offline.<clinit>(Offline.java:31)
10-02 23:55:37.746: W/System.err(7917): at org.jacoco.agent.rt.internal_8ff85ea.Offline.getProbes(Offline.java:51)
10-02 23:55:37.746: W/System.err(7917): at com.example.idea.MainActivity.$jacocoInit(Unknown Source:12)
10-02 23:55:37.746: W/System.err(7917): at com.example.idea.MainActivity.<init>(Unknown Source:0)
10-02 23:55:37.746: W/System.err(7917): at java.lang.Class.newInstance(Native Method)
10-02 23:55:37.746: W/System.err(7917): at android.app.Instrumentation.newActivity(Instrumentation.java:1174)
10-02 23:55:37.746: W/System.err(7917): at android.support.test.runner.MonitoringInstrumentation.newActivity(MonitoringInstrumentation.java:754)
10-02 23:55:37.746: W/System.err(7917): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2669)
10-02 23:55:37.746: W/System.err(7917): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2856)
10-02 23:55:37.746: W/System.err(7917): at android.app.ActivityThread.-wrap11(Unknown Source:0)
10-02 23:55:37.747: W/System.err(7917): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1589)
10-02 23:55:37.747: W/System.err(7917): at android.os.Handler.dispatchMessage(Handler.java:106)
10-02 23:55:37.747: W/System.err(7917): at android.os.Looper.loop(Looper.java:164)
10-02 23:55:37.747: W/System.err(7917): at android.app.ActivityThread.main(ActivityThread.java:6494)
10-02 23:55:37.747: W/System.err(7917): at java.lang.reflect.Method.invoke(Native Method)
10-02 23:55:37.747: W/System.err(7917): at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
10-02 23:55:37.747: W/System.err(7917): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
Глядя на пару других потоков с несколько похожими ошибками, я вижу несколько предложений добавления:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
На мой AndroidManifest.xml, который я уже сделал.
А также:
debug { testCoverageEnabled true }
В файле build.gradle моего приложения.
Youтакже можно увидеть полную конфигурацию jacoco здесь:
apply plugin: 'jacoco'
jacoco {
toolVersion = "0.8.4"
}
def coverageSourceDirs = [
'src/main/java',
'src/debug/java'
]
tasks.withType(Test) {
jacoco.includeNoLocationClasses = true
jacoco.destinationFile = file("$buildDir/jacoco/jacocoTest.exec")
}
task jacocoTestReport(type : JacocoReport, dependsOn : 'testDebugUnitTest') {
group = 'Reporting'
description = 'Generate JaCoCo coverage reports'
reports {
xml.enabled = true
html.enabled = true
}
classDirectories = fileTree(
dir : 'build/intermediates/classes/debug',
excludes : [
'**/R.class',
'**/R$*.class',
'**/*$ViewInjector*.*',
'**/*$ViewBinder*.*',
'**/BuildConfig.*',
'**/Manifest*.*',
'**/*RealmProxy.*',
'**/*ColumnInfo.*',
'**/*RealmModule*.*',
'**/AutoValue_*.*',
'**/Dagger*.*',
'**/*Module_Provide*Factory.*',
'**/*_Factory.*',
'**/*_MembersInjector.*',
'**/*_LifecycleAdapter.*'
]
)
sourceDirectories = files(coverageSourceDirs)
executionData = fileTree(
dir : "$buildDir",
include : [ 'jacoco/testDebugUnitTest.exec', 'outputs/code-coverage/connected/*coverage.ec' ]
)
doFirst {
files('build/intermediates/classes/debug').getFiles().each { file ->
if (file.name.contains('$$')) {
file.renameTo(file.path.replace('$$', '$'))
}
}
}
}
task jacocoTestReportLocal(type : JacocoReport, dependsOn : ['testDebugUnitTest', 'createDebugCoverageReport']) {
group = 'Reporting'
description = 'Generate JaCoCo coverage reports'
reports {
xml.enabled = true
html.enabled = true
}
classDirectories = fileTree(
dir : 'build/intermediates/classes/debug',
excludes : [
'**/R.class',
'**/R$*.class',
'**/*$ViewInjector*.*',
'**/*$ViewBinder*.*',
'**/BuildConfig.*',
'**/Manifest*.*',
'**/*RealmProxy.*',
'**/*ColumnInfo.*',
'**/*RealmModule*.*',
'**/AutoValue_*.*',
'**/Dagger*.*',
'**/*Module_Provide*Factory.*',
'**/*_Factory.*',
'**/*_MembersInjector.*',
'**/*_LifecycleAdapter.*',
'**/models/**'
]
)
sourceDirectories = files(coverageSourceDirs)
executionData = fileTree(
dir : "$buildDir",
include : [ 'jacoco/testDebugUnitTest.exec', 'outputs/code-coverage/connected/*coverage.ec' ]
)
doFirst {
files('build/intermediates/classes/debug').getFiles().each { file ->
if (file.name.contains('$$')) {
file.renameTo(file.path.replace('$$', '$'))
}
}
}
}
И полную сборку приложения build.gradle здесь:
apply plugin: 'com.android.application'
apply from: '../jacoco.gradle'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.example.idea"
minSdkVersion 22
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug { testCoverageEnabled true }
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support:support-media-compat:28.0.0'
implementation 'com.android.support:support-v4:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'com.google.firebase:firebase-analytics:15.0.0'
// implementation 'com.google.firebase:firebase-core:15.0.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
androidTestImplementation 'com.android.support.test:rules:1.0.2'
}
apply plugin: 'com.google.gms.google-services'
, а также config.yml здесь:
version: 2
references:
## Cache
cache_key: &cache_key
key: cache-{{ checksum "gradle/wrapper/gradle-wrapper.properties" }}-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }}
restore_cache: &restore_cache
restore_cache:
<<: *cache_key
save_cache: &save_cache
save_cache:
<<: *cache_key
paths:
- ~/.gradle
- ~/.m2
## Workspace
workspace: &workspace
~/workspace
attach_debug_workspace: &attach_debug_workspace
attach_workspace:
at: *workspace
attach_release_workspace: &attach_release_workspace
attach_workspace:
at: *workspace
persist_debug_workspace: &persist_debug_workspace
persist_to_workspace:
root: *workspace
paths:
- app/build/outputs/androidTest-results
- app/build/outputs/apk
- app/build/outputs/code-coverage
- app/build/test-results
persist_release_workspace: &persist_release_workspace
persist_to_workspace:
root: *workspace
paths:
- app/build
attach_firebase_workspace: &attach_firebase_workspace
attach_workspace:
at: *workspace
persist_firebase_workspace: &persist_firebase_workspace
persist_to_workspace:
root: *workspace
paths:
- firebase
## Docker image configuration
android_config: &android_config
working_directory: *workspace
docker:
- image: circleci/android:api-28-alpha
environment:
TERM: dumb
_JAVA_OPTIONS: "-Xmx2048m -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap"
GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m"'
gcloud_config: &gcloud_config
working_directory: *workspace
docker:
- image: google/cloud-sdk:206.0.0
environment:
TERM: dumb
# Google Services
export_gservices_key: &export_gservices_key
run:
name: Export Google Services key environment variable
command: echo 'export GOOGLE_SERVICES_KEY="$GOOGLE_SERVICES_KEY"' >> $BASH_ENV
decode_gservices_key: &decode_gservices_key
run:
name: Decode Google Services key
command: echo $GOOGLE_SERVICES_KEY | base64 -di > app/google-services.json
# Google Cloud Service
export_gcloud_key: &export_gcloud_key
run:
name: Export Google Cloud Service key environment variable
command: echo 'export GCLOUD_SERVICE_KEY="$GCLOUD_SERVICE_KEY"' >> $BASH_ENV
decode_gcloud_key: &decode_gcloud_key
run:
name: Decode Google Cloud credentials
command: echo $GCLOUD_SERVICE_KEY | base64 -di > ${HOME}/client-secret.json
jobs:
# Build debug APK for unit tests and an instrumented test APK
build_debug:
<<: *android_config
steps:
- checkout
- *restore_cache
- run:
name: Download dependencies
command: ./gradlew androidDependencies
- *save_cache
- *export_gservices_key
- *decode_gservices_key
- run:
name: Gradle build (debug)
command: ./gradlew -PciBuild=true :app:assembleDebug :app:assembleAndroidTest
- *persist_debug_workspace
- store_artifacts:
path: app/build/outputs/apk/
destination: /apk/
# Build release APK
build_release:
<<: *android_config
steps:
- checkout
- *restore_cache
- run:
name: Download dependencies
command: ./gradlew androidDependencies
- *save_cache
- *export_gservices_key
- *decode_gservices_key
- run:
name: Gradle build (release)
command: ./gradlew -PciBuild=true :app:assembleRelease
- *persist_release_workspace
- store_artifacts:
path: app/build/outputs/apk/
destination: /apk/
- store_artifacts:
path: app/build/outputs/mapping/
destination: /mapping/
# Run unit tests
test_unit:
<<: *android_config
steps:
- checkout
- *restore_cache
- run:
name: Download dependencies
command: ./gradlew androidDependencies
- *save_cache
- *export_gservices_key
- *decode_gservices_key
- run:
name: Run unit tests
command: ./gradlew -PciBuild=true :app:testDebugUnitTest
- *persist_debug_workspace
- store_artifacts:
path: app/build/reports/
destination: /reports/
- store_test_results:
path: app/build/test-results/
destination: /test-results/
# Run instrumented tests
test_instrumented:
<<: *gcloud_config
steps:
- *attach_debug_workspace
- *export_gcloud_key
- *decode_gcloud_key
- run:
name: Set Google Cloud target project
command: gcloud config set project i-de-a
- run:
name: Authenticate with Google Cloud
command: gcloud auth activate-service-account firebase-adminsdk-3t8n8@i-de-a.iam.gserviceaccount.com --key-file ${HOME}/client-secret.json
- run:
name: Echo sha1 variable for debugging
command: echo ${CIRCLE_SHA1}
- run:
name: Run instrumented test on Firebase Test Lab
command: gcloud firebase test android run --type instrumentation --app app/build/outputs/apk/debug/app-debug.apk --test app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk --device model=Nexus6P,version=27,locale=en_US,orientation=portrait --no-use-orchestrator --environment-variables coverage=true,coverageFile="/sdcard/coverage.ec" --directories-to-pull=/sdcard --timeout 20m --results-dir=${CIRCLE_SHA1}
- run:
name: Create directory to store test results
command: mkdir firebase
- run:
name: Download instrumented test results from Firebase Test Lab
command: gsutil -m cp -r -U gs://test-lab-hzda62mwyy730-n7hcz7bxtxrx0/${CIRCLE_SHA1} /root/workspace/firebase/
- *persist_firebase_workspace
- store_artifacts:
path: firebase/
destination: /firebase/
# Submit JaCoCo coverage report
report_coverage:
<<: *android_config
steps:
- checkout
- *restore_cache
- run:
name: Download dependencies
command: ./gradlew androidDependencies
- *attach_debug_workspace
- *attach_firebase_workspace
- run:
name: Move Firebase coverage report
command: mkdir -p app/build/outputs/code-coverage/connected && cp firebase/${CIRCLE_SHA1}/Nexus6P-27-en_US-portrait/artifacts/coverage.ec app/build/outputs/code-coverage/connected/coverage.ec
- *export_gservices_key
- *decode_gservices_key
- run:
name: Generate JaCoCo report
command: ./gradlew -PciBuild=true :app:jacocoTestReport
- run:
name: Upload coverage report to CodeCov
command: bash <(curl -s https://codecov.io/bash)
- store_artifacts:
path: app/build/reports/
destination: /reports/
workflows:
version: 2
workflow:
jobs:
- build_debug
- build_release
- test_unit
- test_instrumented:
requires:
- build_debug
- report_coverage:
requires:
- build_release
- test_unit
- test_instrumented
https://github.com/kyleo83/idea
Вы можете увидеть полный код на этом GitHub.
Любая помощь здесь будет принята с благодарностью!