Android 仪器化单元测试(instrumented unit tests) Androidx kotlin版本(1)

  • 配置build.gradle依赖

dependencies {

androidTestImplementation ‘androidx.test🏃1.4.0’

androidTestImplementation ‘androidx.test:rules:1.4.0’

}

android {

defaultConfig {

testInstrumentationRunner “androidx.test.runner.AndroidJUnitRunner”

}

}

测试样例


测试代码

本例子参考google官方网站。因为官网里面很多类没有给到,所以进行了一波补充完善。➡️➡️➡️➡️点击可前往官网

以下示例显示了如何编写仪器单元测试来验证 Parcelable接口是否被 LogHistory类正确实现:🙆‍♀️

import android.os.Parcel

import android.os.Parcelable

import android.os.Parcelable.Creator

import androidx.test.runner.AndroidJUnit4

import org.junit.Assert

import org.junit.Before

import org.junit.Test

import org.junit.runner.RunWith

const val TEST_STRING = “This is a string”

const val TEST_LONG = 12345678L

// @RunWith is required only if you use a mix of JUnit3 and JUnit4.

@RunWith(AndroidJUnit4::class)

class LogHistoryAndroidUnitTest {

private lateinit var logHistory: LogHistory

@Before

fun createLogHistory() {

logHistory = LogHistory(TEST_STRING, TEST_LONG)

}

@Test

fun logHistory_ParcelableWriteRead() {

val parcel = Parcel.obtain()

logHistory.apply {

// 写数据

writeToParcel(parcel, describeContents())

}

// 完成写入后,置包裹以进行读取。

parcel.setDataPosition(0)

// 读取数据

val createdFromParcel: LogHistory = LogHistory.CREATOR.createFromParcel(parcel)

// 验证数据的正确性

Assert.assertTrue(“error TEST_STRING”, createdFromParcel.strValue == TEST_STRING)

Assert.assertTrue(“error TEST_LONG”, createdFromParcel.longValue == TEST_LONG)

}

}

class LogHistory(val strValue: String = “”, val longValue: Long = 0L) : Parcelable {

constructor(source: Parcel) : this(source.readString() ?: “”, source.readLong())

companion object {

val CREATOR: Creator = object : Creator {

override fun createFromParcel(source: Parcel?): LogHistory = LogHistory(source!!)

override fun newArray(size: Int): Array = Array(size) { LogHistory() }

}

}

override fun describeContents(): Int = 0

override fun writeToParcel(dest: Parcel?, flags: Int) {

dest!!.writeString(strValue)

dest.writeLong(longValue)

}

}

运行

  1. 确保已经链接手机

  2. 点击下图2所示的箭头

  3. 点击run在真机上运行

结果

​ 通过测试结果可以清晰看到状态passed,代表测试成功。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2jjQOJw0-1639124297064)(E:\Documentation\My_Learn_Documentation_Git\android\pic_android\run_result.png)]

​ 那如果把Assert.assertTrue("error TEST_LONG", createdFromParcel.longValue == TEST_LONG)改为Assert.assertTrue("error TEST_LONG", createdFromParcel.longValue == 0L呢?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mltP6P50-1639124297064)(E:\Documentation\My_Learn_Documentation_Git\android\pic_android\run_error.png)]

​ 可以看到巨大的failed,还可以点开错误日志,看到报错的信息!!!

一键测试所有


以上的测试只能一个一个的点,如果我写好了一堆单元测试,想要一键测试,应该怎么做呢?😮

引入

下面引入@Suite.SuiteClasses

  • 将需要进行一键测试类上方增加@Suite.SuiteClasses注解

  • 新建类的入口,引入需要一键执行的类,并且需要新增@RunWith(Suite::class)与@Suite.SuiteClasses(Class...)注解

举个🌰

  • 我们有两个类都写了单元测试,如下所示:

最后

跳槽季整理面试题已经成了我多年的习惯!在这里我和身边一些朋友特意整理了一份快速进阶为Android高级工程师的系统且全面的学习资料。涵盖了Android初级——Android高级架构师进阶必备的一些学习技能。

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)


《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

[外链图片转存中…(img-71gVWokM-1714558257762)]
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

plugins { id 'com.android.application' } apply from: 'jacoco-report.gradle' def cfg = rootProject.ext.configuration android { compileSdk cfg.targetSdk defaultConfig { applicationId cfg.applicationId minSdk cfg.minSdk targetSdk cfg.targetSdk versionCode cfg.versionCode versionName cfg.versionName } signingConfigs { release { storeFile file("$rootDir/platform.jks") storePassword = "123456" keyPassword = "123456" keyAlias = "ktplatfrom" } debug { storeFile file("$rootDir/platform.jks") storePassword = "123456" keyPassword = "123456" keyAlias = "ktplatfrom" } } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } debugCoverage { minifyEnabled false testCoverageEnabled false } } // buildTypes { // release { // minifyEnabled false // signingConfig signingConfigs.release // } // debug { // minifyEnabled false // signingConfig signingConfigs.debug // debuggable true // testCoverageEnabled false // } // } applicationVariants.all { variant -> variant.outputs.all { output -> def newName = "ktLauncher.apk" outputFileName = newName } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } dataBinding { enabled = true } buildFeatures { aidl true } //儿童模式 flavorDimensions "default" productFlavors { _NORMAL_MODE { buildConfigField("int", "user_model", "0") } _CHILD_MODE { buildConfigField("int", "user_model", '1') } } testOptions { unitTests { includeAndroidResources = true returnDefaultValues = true } } } gradle.projectsEvaluated { tasks.withType(JavaCompile) { Set<File> fileSet = options.bootstrapClasspath.getFiles() List<File> newFileList = new ArrayList<>(); //将framework.jar插入到最前面 newFileList.add(new File("libs/framework.jar")) //最后将原始的数据插入 newFileList.addAll(fileSet) options.bootstrapClasspath = files( newFileList.toArray() ) } } dependencies { // api fileTree(include: ['*.jar'], dir: 'libs') implementation rootProject.ext.dependencies["appcompat"] implementation rootProject.ext.dependencies["material"] implementation rootProject.ext.dependencies["constraintlayout"] implementation rootProject.ext.dependencies["lifecycle-extensions"] implementation rootProject.ext.dependencies["gson"] implementation project(':base') compileOnly files('libs/framework.jar') implementation 'com.github.bumptech.glide:glide:4.12.0' implementation 'com.android.support:design:28.0.0' implementation "androidx.constraintlayout:constraintlayout:2.1.3" annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0' // test testImplementation 'org.powermock:powermock-module-junit4:2.0.7' testImplementation 'org.powermock:powermock-module-junit4-rule:1.7.4' testImplementation 'org.powermock:powermock-api-mockito2:2.0.7' testImplementation 'org.mockito:mockito-core:3.3.3' testImplementation 'android.arch.core:core-testing:1.1.1' testImplementation 'org.mockito:mockito-inline:3.11.2' } -----build.gradle----------- apply plugin: 'jacoco' //gradlew clean & gradlew createOfflineTestCoverageReport & gradlew jacocoTestReport tasks.withType(Test) { jacoco.includeNoLocationClasses = true jacoco.excludes = ['jdk.internal.*'] } configurations { jacocoAnt jacocoRuntime } jacoco { toolVersion = "0.8.5" } def offline_instrumented_outputDir = "$buildDir.path/intermediates/classes-instrumented/debugCoverage" tasks.withType(Test) { jacoco.includeNoLocationClasses = true jacoco.excludes = ['jdk.internal.*'] } def coverageSourceDirs = [ 'src/main/java' ] task jacocoTestReport(type: JacocoReport, dependsOn: "test") { group = "Reporting" description = "Generate Jacoco coverage reports" classDirectories.from = fileTree( dir: './build/intermediates/javac/debugCoverage/classes/', excludes: [ '**/R.class', '**/R$*.class', '**/Manifest*.*', '**/*Test*.*', 'android/**/*.*', '**/BuildConfig.*', '**/**Bean.class', '**/inputmethod/*', '**/config/*.*', '**/flex/*.*', '**/AssetsUtils.class' ] ) getSourceDirectories().setFrom(files(coverageSourceDirs)) getExecutionData().setFrom(files('./build/jacoco/testDebugUnitTest.exec')) } jacocoTestReport { reports { xml.enabled true html.enabled true html.destination file("build/test-results/jacocoHtml") } } /* This task is used to create offline instrumentation of classes for on-the-fly instrumentation coverage tool like Jacoco. See jacoco classId * and Offline Instrumentation from the jacoco site for more info. * * In this case, some classes mocked using PowerMock were reported as 0% coverage on jacoco & Sonarqube. The issue between PowerMock and jacoco * is well documented, and a possible solution is offline Instrumentation (not so well documented for gradle). * * In a nutshell, this task: * - Pre-instruments the original *.class files * - Puts the instrumented classes path at the beginning of the task's classpath (for report purposes) * - Runs test & generates a new exec file based on the pre-instrumented classes -- as opposed to on-the-fly instrumented class files generated by jacoco. * * It is currently not implemented to run prior to any other existing tasks (like test, jacocoTestReport, etc...), therefore, it should be called * explicitly if Offline Instrumentation report is needed. * * Usage: gradle clean & gradle createOfflineInstrTestCoverageReport & gradle jacocoTestReport * - gradle clean //To prevent influence from any previous task execution * - gradle createOfflineInstrTestCoverageReport //To generate *.exec file from offline instrumented class * - gradle jacocoTestReport //To generate html report from newly created *.exec task */ task createOfflineTestCoverageReport(dependsOn: ['instrument', 'testDebugCoverageUnitTest']) { group = "reporting" doLast { ant.taskdef(name: 'report', classname: 'org.jacoco.ant.ReportTask', classpath: configur3+ations.jacocoAnt.asPath) ant.report() { executiondata { ant.file(file: "$buildDir.path/jacoco/testDebugCoverageUnitTest.exec") } structure(name: 'Example') { classfiles { fileset(dir: "$project.buildDir/intermediates/javac/debugCoverage") } sourcefiles { fileset(dir: 'src/main/java') } } //Uncomment if we want the task to generate jacoco html reports. However, the current script does not exclude files. //An alternative is to used jacocoTestReport after this task finishes //html(destdir: "$buildDir.path/reports/jacocoHtml") } } } createOfflineTestCoverageReport.dependsOn(clean) /* * Part of the Offline Instrumentation process is to add the jacoco runtime to the class path along with the path of the instrumented files. */ gradle.taskGraph.whenReady { graph -> if (graph.hasTask(instrument)) { tasks.withType(Test) { doFirst { systemProperty 'jacoco-agent.destfile', buildDir.path + '/jacoco/testDebugCoverageUnitTest.exec' classpath = files(offline_instrumented_outputDir) + classpath + configurations.jacocoRuntime } } } } /* * Instruments the classes per se */ task instrument(dependsOn:'compileDebugUnitTestSources') { doLast { println 'Instrumenting classes' ant.taskdef(name: 'instrument', classname: 'org.jacoco.ant.InstrumentTask', classpath: configurations.jacocoAnt.asPath) ant.instrument(destdir: offline_instrumented_outputDir) { fileset(dir: "$buildDir.path/intermediates/javac/debugCoverage") } } } -----jacoco-report.gradle--------------- jacoco-report.gradle 在同一个项目中的base模块可以正常生成jacoco单元测试覆盖率报告,但是在app模块无法生成,build文件夹中,都没有jacoco和 test-results文件夹。上述的jacoco-report.gradle和build.gradle是app模块的内容 请问应该如何解决app模块无法显示jacoco报告的问题i
最新发布
07-23
<think>我们正在处理一个Android项目中使用Jacoco生成单元测试覆盖率报告的问题。用户已经配置了Jacoco,但无法生成test-results和jacoco报告文件夹。我们需要修复配置以生成这些报告。 根据引用[1]的内容,我们知道在Android项目中配置Jacoco生成测试覆盖率报告可能会遇到一些困难,因为Android测试工具在快速变,而且网上找到的解决方案可能不总是有效。 常见的配置步骤和可能的问题点: 1. 确保在build.gradle文件中应用了Jacoco插件: ```groovy apply plugin: 'jacoco' ``` 2.android块中设置testOptions以启用单元测试的报告(对于Android Gradle插件3.0.0及以上版本): ```groovy android { ... testOptions { unitTests.all { jacoco { includeNoLocationClasses = true // 你可能需要这个选项来避免某些问题,特别是当使用AndroidX测试时 } } // 或者,如果你使用Android Gradle插件4.0及以上,可以这样配置: unitTests { includeAndroidResources = true } } } ``` 3. 添加一个任务来生成Jacoco报告。通常,我们会创建一个自定义的Jacoco报告任务,它依赖于测试任务(如testDebugUnitTest)并生成报告。 4. 注意:在较新的Android Gradle插件版本中,测试任务名称可能已经改变。例如,对于debug构建类型的单元测试任务名称可能是`testDebugUnitTest`。 5. 确保在运行测试时Jacoco代理被正确配置。Jacoco插件会自动配置Java代理来收集覆盖率数据,但有时在Android项目中可能不会自动应用,特别是对于单元测试(在test source set中)。 6. 生成报告的任务示例: ```groovy task jacocoTestReport(type: JacocoReport, dependsOn: ['testDebugUnitTest']) { group = "Reporting" description = "Generate Jacoco coverage reports" reports { xml.enabled = true html.enabled = true } // 需要指定哪些文件需要收集覆盖率数据 def fileFilter = [ '**/*ViewModel.*', '**/*Repository.*', // 排除自动生成的类、Android框架类等 '**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*Test*.*', 'android/**/*.*' ] // 这里指定class目录和源码目录 def javaClasses = fileTree(dir: "${buildDir}/intermediates/javac/debug", excludes: fileFilter) def kotlinClasses = fileTree(dir: "${buildDir}/tmp/kotlin-classes/debug", excludes: fileFilter) classDirectories.from = files([javaClasses, kotlinClasses]) sourceDirectories.from = files([ "$project.projectDir/src/main/java", "$project.projectDir/src/main/kotlin" ]) executionData.from = files("${buildDir}/jacoco/testDebugUnitTest.exec") } ``` 7. 注意:在较新的Android Studio版本中,构建目录结构可能发生变。例如,Kotlin类的编译输出路径可能是`${buildDir}/tmp/kotlin-classes/debug`,而Java类的路径可能是`${buildDir}/intermediates/javac/debug`。但请注意,这个路径可能因Gradle插件版本不同而变。 8. 如果使用Kotlin,并且使用了`kotlin-android`插件,可能需要确保Kotlin的编译任务与Jacoco兼容。 9. 运行任务:在终端中运行`./gradlew jacocoTestReport`(或通过Gradle面板运行该任务)来生成报告。 10. 如果仍然无法生成报告,请检查: - 单元测试是否真的运行了(检查测试是否通过,是否有测试代码)。 - 执行数据文件(如`testDebugUnitTest.exec`)是否生成。如果没有,可能是测试任务没有被正确执行,或者Jacoco代理没有被正确附加。 - 检查构建日志,看是否有关于Jacoco的警告或错误。 11. 对于Android项目,有时需要为instrumented testsAndroidTest)和unit teststest)分别配置。这里我们主要关注单元测试test)。 12. 另外,注意在Android Gradle插件4.0及以上版本中,测试任务名称中的“Debug”可能变成了“DebugUnitTest”,而之前可能是“testDebug”。 13. 如果项目使用了多种构建类型(如release)和产品变种(flavor),则需要相应地调整任务名称和路径。 14. 如果以上配置仍然无效,可以尝试更新Jacoco版本Android Gradle插件版本,因为较新的版本可能修复了相关的问题。 根据引用[1]的提示,我们也可以参考一些最新的社区解决方案,因为Android工具链变较快。 因此,我们提供以下修复步骤: 步骤1:在app模块的build.gradle文件中应用jacoco插件,并指定jacoco版本(可选,如果不指定则使用Gradle默认版本): ```groovy buildscript { repositories { google() jcenter() } dependencies { classpath "org.jacoco:org.jacoco.core:0.8.7" // 使用你需要的版本 } } apply plugin: 'jacoco' ``` 步骤2:配置testOptions(根据你的Android Gradle插件版本调整): ```groovy android { ... testOptions { unitTests.all { // 配置Jacoco jacoco { includeNoLocationClasses = true // 注意:在最新版本的Jacoco中,可能不需要这个选项,但如果你遇到类找不到的问题,可以加上 excludes = ['jdk.internal.*'] // 可能需要排除一些类 } } } } ``` 步骤3:定义生成报告的任务(如上所示),注意调整路径。 步骤4:运行单元测试任务(例如`testDebugUnitTest`)和生成的报告任务(`jacocoTestReport`)。 步骤5:检查报告是否生成。报告通常位于`build/reports/jacoco/jacocoTestReport/`目录下。 如果遇到问题,可以尝试以下常见解决方案: - 确保在运行报告生成任务之前,单元测试任务已经运行(或者将报告任务依赖于测试任务,如上所示)。 - 检查执行数据文件路径是否正确。有时执行数据文件可能被放在不同的位置,例如:`${buildDir}/outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec`(这取决于你的Gradle版本和配置)。 - 如果使用Kotlin,并且使用`kotlin-android`插件,请确保Kotlin编译任务在Jacoco任务之前运行(通常依赖关系会自动处理)。 另外,注意在Android项目中,单元测试test)和仪器测试(androidTest)是分开的。我们这里配置的是单元测试。 如果用户同时需要仪器测试的覆盖率,则需要额外的配置,但问题中只提到了单元测试。 最后,如果用户使用的是最新版本Android Studio和Gradle插件,也可以考虑使用Android Studio内置的覆盖率工具(但Jacoco更灵活)。 由于问题描述中已经提到无法生成test-results和jacoco报告文件夹,我们还需要确保测试任务本身被正确执行。请检查: - 是否有测试代码? - 测试是否通过?如果测试失败,可能不会生成报告(取决于配置)。 如果以上配置都正确,但仍然无法生成报告,请提供具体的构建日志或错误信息,以便进一步排查。 现在,我们生成相关问题:</think>### 解决 Android Jacoco 覆盖率报告生成问题 以下是针对 Android Gradle 模块无法生成 Jacoco 单元测试覆盖率报告的完整解决方案: #### 1. **基础配置修复** 在 `app/build.gradle` 中确保以下配置正确: ```groovy android { buildTypes { debug { testCoverageEnabled = true // 关键开关 } } } apply plugin: 'jacoco' jacoco { toolVersion = "0.8.8" // 使用最新兼容版本 } task jacocoTestReport(type: JacocoReport, dependsOn: 'testDebugUnitTest') { group = "Reporting" description = "生成 Jacoco 覆盖率报告" reports { xml.enabled = true // 需 XML 报告 html.enabled = true // 需 HTML 报告 } // 指定类文件和源码目录 def classDir = fileTree( dir: "${buildDir}/intermediates/javac/debug/classes", excludes: [ // 排除不需要分析的类 '**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*' ] ) classDirectories.from = files([classDir]) sourceDirectories.from = files(["src/main/java"]) executionData.from = files("${buildDir}/jacoco/testDebugUnitTest.exec") // 执行数据路径 } ``` #### 2. **关键修复点 1. **执行数据路径修复**: ```gradle // 新版本 Android Gradle 插件路径变更: executionData.from = fileTree(dir: buildDir, includes: [ "outputs/unit_test_code_coverage/debugUnitTest/*.exec" ]) ``` 2. **Kotlin 项目特殊处理**: ```gradle // 添加 Kotlin 类目录 def kotlinClassDir = fileTree( dir: "${buildDir}/tmp/kotlin-classes/debug", excludes: [...] ) classDirectories.from = files([classDir, kotlinClassDir]) ``` 3. **启用测试覆盖率**: ```gradle android { testOptions { unitTests.all { jacoco { includeNoLocationClasses = true // 修复 AndroidX 问题 excludes = ['jdk.internal.*'] // Java 11+ 兼容性 } } } } ``` #### 3. **执行流程** 1. 运行测试生成数据: ```bash ./gradlew testDebugUnitTest ``` 2. 生成报告: ```bash ./gradlew jacocoTestReport ``` 3. 查看报告: - HTML 报告:`app/build/reports/jacoco/jacocoTestReport/html/index.html` - XML 报告:`app/build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml` #### 4. **常见问题排查 - **无 `.exec 文件生成**:检查 `testCoverageEnabled = true` 是否启用 - **空报告 (0% 覆盖率)**:确认 `classDirectories` 路径包含实际类文件 - **Java 11+ 兼容问题**:添加 `excludes = ['jdk.internal.*']` - **AndroidX 测试支持**:添加 `includeNoLocationClasses = true` - **变体构建 (Flavors)**:将 `debug` 替换为具体变体名(如 `demoDebug`) > 提示:Android Gradle 插件 7.0+ 需使用 [新路径规则](https://developer.android.com/reference/tools/gradle-api/7.0/com/android/build/api/dsl/UnitTestOptions#jacoco()),旧版路径 `outputs/code-coverage/` 已废弃[^1]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值