测试覆盖率 Jacoco 统计 Android 代码覆盖率 [instrument 方式]

在路上 · 2018年10月09日 · 最后由 ningboRainbow 回复于 2023年06月20日 · 11003 次阅读
本帖已被设为精华帖!

示例 JacocoTestForAndroid 见:https://github.com/OnTheWay111/JacocoTestForAndroid
还有一个更灵活的非 instrument 方式: [定制触发条件] jacoco 统计 Android 代码覆盖率
本文两年前的代码,有些代码可能已过时,如果遇到问题,可参考:https://www.jianshu.com/p/ab2be01d7347#comment-60999311

1、背景

最近团队想基于 monkey,进行 app 遍历开发,为了与市场上的遍历方案做对比,以及团队遍历方案的优化比较,故选择了 APP 的代码覆盖率统计。

备注:Instrumentation 方式 1 和 Instrumentation 方式 2 几乎一样

2、代码覆盖率方案选型

目前 Java 常用覆盖率工具 Jacoco、Emma、Cobertura 和 Clover(商用)

Jacoco 是一个开源的覆盖率工具。Jacoco 可以嵌入到 Ant 、Maven 中,并提供了 EclEmma Eclipse 插件,也可以使用 Java Agent 技术监控 Java 程序。很多第三方的工具提供了对 Jacoco 的集成,如:Sonar、Jenkins、IDEA.
综合考虑:选择 jacoco 作为代码覆盖率统计工具。

3、jacoco 注入原理


本次,在尽量避免修改源代码的情况下,我们选择 instrumentation 模式,开始操作。

4、jacoco 注入实操 (instrument 方式)

4.1 创建一个安卓项目

随便创建一个简单的安卓项目

4.2 在代码主路径下新建 test 文件夹,新建 3 个类文件

在 java/下新建 test 文件夹,然后在 test 路径下
新建三个类文件,分别是抽象类 FinishListener,Instrumentation 的 Activity 和 instrumentation 类。

  • FinishListener:
public interface FinishListener {
    void onActivityFinished();
    void dumpIntermediateCoverage(String filePath);
}
  • InstrumentedActivity:
public class InstrumentedActivity extends MainActivity {
    public static String TAG = "IntrumentedActivity";
    private FinishListener mListener;
    public void setFinishListener(FinishListener listener) {
        mListener = listener;
    }
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG + ".InstrumentedActivity", "onDestroy()");
        super.finish();
        if (mListener != null) {
            mListener.onActivityFinished();
        }
    }
}
  • JacocoInstrumentation:
public class JacocoInstrumentation extends Instrumentation implements FinishListener {
    public static String TAG = "JacocoInstrumentation:";
    private static String DEFAULT_COVERAGE_FILE_PATH = "/mnt/sdcard/coverage.ec";
    private final Bundle mResults = new Bundle();
    private Intent mIntent;
    private static final boolean LOGD = true;
    private boolean mCoverage = true;
    private String mCoverageFilePath;

    public JacocoInstrumentation() {
    }

    @Override
    public void onCreate(Bundle arguments) {
        Log.d(TAG, "onCreate(" + arguments + ")");
        super.onCreate(arguments);
        DEFAULT_COVERAGE_FILE_PATH = getContext().getFilesDir().getPath().toString() + "/coverage.ec";
        File file = new File(DEFAULT_COVERAGE_FILE_PATH);
        if(!file.exists()){
            try{
                file.createNewFile();
            }catch (IOException e){
                Log.d(TAG,"新建文件异常:"+e);
                e.printStackTrace();}
        }
        if(arguments != null) {
            mCoverageFilePath = arguments.getString("coverageFile");
        }
        mIntent = new Intent(getTargetContext(), InstrumentedActivity.class);
        mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        start();
    }

    @Override
    public void onStart() {
        super.onStart();
        Looper.prepare();
        InstrumentedActivity activity = (InstrumentedActivity) startActivitySync(mIntent);
        activity.setFinishListener(this);
    }

    private String getCoverageFilePath() {
        if (mCoverageFilePath == null) {
            return DEFAULT_COVERAGE_FILE_PATH;
        } else {
            return mCoverageFilePath;
        }
    }

    private void generateCoverageReport() {
        Log.d(TAG, "generateCoverageReport():" + getCoverageFilePath());
        OutputStream out = null;
        try {
            out = new FileOutputStream(getCoverageFilePath(), false);
            Object agent = Class.forName("org.jacoco.agent.rt.RT")
                    .getMethod("getAgent")
                    .invoke(null);
            out.write((byte[]) agent.getClass().getMethod("getExecutionData", boolean.class)
                    .invoke(agent, false));
        } catch (Exception e) {
            Log.d(TAG, e.toString(), e);
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    @Override
    public void onActivityFinished() {
        if (LOGD)      Log.d(TAG, "onActivityFinished()");
        if (mCoverage) {
            generateCoverageReport();
        }
        finish(Activity.RESULT_OK, mResults);
    }

    @Override
    public void dumpIntermediateCoverage(String filePath) {

    }
}

4.3 修改 Build.gradle 文件

增加 jacoco 插件和打开覆盖率统计开关

apply plugin: 'com.android.application'
apply plugin: 'jacoco'

android {
    compileSdkVersion 28
    buildToolsVersion "28.0.2"
    defaultConfig {
        applicationId "com.example.app1"
        minSdkVersion 24
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.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.constraint:constraint-layout:1.1.3'
    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'
}

4.4 在 manifest.xml 增加声明

  • 在中声明 InstrumentedActivity:
<activity android:label="InstrumentationActivity"    android:name="coverage.netease.com.test.InstrumentedActivity" />
  • 在 manifest 中声明使用 SD 卡权限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
  • 最后,在 manifest 中单独声明 JacocoInstrumentation:
<instrumentation
        android:handleProfiling="true"
        android:label="CoverageInstrumentation"
        /*这里android:name写明此instrumentation的全称*/
        android:name="com.example.test.JacocoInstrumentation"
        /*这里android:targetPackage写明被测应用的包名*/
        android:targetPackage="com.example.app1"/>

4.5 通过 gradle installDebug 安装到设备上

4.6 在命令行下通过 adb shell am instrument 命令调起 app,具体命令是:

adb shell am instrument com.example.app1/com.example.test.JacocoInstrumentation

也就是 adb shell am instrument /

4.7 调起 app 后我们就可以进行手工测试了,测试完成后点击返回键退出 app。

此时,jacoco 便将覆盖率统计信息写入/data/data//files/coverage.ec 文件。
接下来我们需要新增 gradle task,分析覆盖率文件生成覆盖率 html 报告。

5、生成 html 报告

5.1 首先将 coverage.ec 文件拉到本地,置于指定目录下。

  • adb shell 进入设备,找到 data/data//files/coverage.ec 文件。
  • adb pull 到 pc 本地。
  • 将该文件拖至入 app 根目录/build/outputs/code-coverage/connected(没有的话,可以执行 gradle createDebugCoverageReport)

5.2 新增 gradle task,修改 build.gradle 文件为:

apply plugin: 'com.android.application'
apply plugin: 'jacoco'

android {
    compileSdkVersion 28
    buildToolsVersion "28.0.2"
    defaultConfig {
        applicationId "com.example.app1"
        minSdkVersion 24
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.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.constraint:constraint-layout:1.1.3'
    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'
}

def coverageSourceDirs = [
        '../app/src/main/java'
]

task jacocoTestReport(type: JacocoReport) {
    group = "Reporting"
    description = "Generate Jacoco coverage reports after running tests."
    reports {
        xml.enabled = true
        html.enabled = true
    }
    classDirectories = fileTree(
            dir: './build/intermediates/app_classes/debug',
            excludes: ['**/R*.class',
                       '**/*$InjectAdapter.class',
                       '**/*$ModuleAdapter.class',
                       '**/*$ViewInjector*.class'
            ])
    sourceDirectories = files(coverageSourceDirs)
    executionData = files("$buildDir/outputs/code-coverage/connected/coverage.ec")

    doFirst {
        new File("$buildDir/intermediates/app_classes/").eachFileRecurse { file ->
            if (file.name.contains('$$')) {
                file.renameTo(file.path.replace('$$', '$'))
            }
        }
    }
}

5.3 生成 html 报告

  • 执行:
gradle jacocoTestReport

执行完以上命令后,html 报告生成

  • 查看 html 报告 报告路径://app/build/reports/jacoco/jacocoTestReport/html

打开报告后,如图所示:

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 79 条回复 时间 点赞

大佬你好,我根据上面的帮助内容,在我的代码工程对应的模块 app 下面创建了 jacoco.gradle 文件,并且在同级目录下的 build.gradle 中也进行了引用,并打开了覆盖率统计开关,并在 app/src/main/java/com/下面创建了 jacoco 目录并新增了三个 java 文件,
然后执行了如下三步:
1、编译 ------ 成功,生成 apk
2、上传到手机设备上: adb install -r xxxnternal-dbg.apk。成功上传。
3、开启测试模式:adb shell am instrument com.xx.xx.xx/.jacoco.JacocoInstrumentation 成功!
4、打开手机 app,进行手工操作测试,然后按返回键退出 app
5、检查 /data/data/com.xx.xx.xx/files/xx.ec 文件是否生成,但是!前面都没有问题,就是这一步出现问题了,没有任何对应的 .ec 文件生成,请问可能的原因是哪些呢?


大神打扰了,问下是否有遇到过 kotlin 有一些写法的语句是覆盖不了

@zailushang 大佬求问,我不能生成覆盖率报告是为什么,这个问题已经困扰我四天了,嘤嘤嘤,找了网上的解决方案,都没有效,特来请教
build.gradle 的配置如下

apply plugin: 'com.android.application'
apply plugin: 'jacoco'

jacoco {
    toolVersion = "0.7.9"
//    reportsDir = file("$buildDir/reports/coverage")
}

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.3"
    defaultConfig {
        applicationId "com.example.sample1"
        minSdkVersion 17
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
        debug {
            /**打开覆盖率统计开关*/
            testCoverageEnabled = true
        }
    }
//    lintOptions {
//        abortOnError false
//    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
}

def coverageSourceDirs = [
        '../app/src/main/java/'
]

task jacocoTestReport(type: JacocoReport) {
    println "aa"
    group = "Reporting"
    description = "Generate Jacoco coverage reports after running tests."
    reports {
        xml.enabled = true
        html.enabled = true
//        html.destination "${buildDir}/reports/jacocoHtml.html"
//        html.destination(file("${buildDir}/reports/jacocoHtml.html"))

    }

    classDirectories.setFrom(files(classDirectories.files.collect {
        fileTree(dir: './build/intermediates/app_classes/debug',
                excludes: ['**/R.class',
                           '**/R$*.class',
                           '**/*$InjectAdapter.class',
                           '**/*$ModuleAdapter.class',
                           '**/*$ViewInjector*.class'
                ])
    }))
    sourceDirectories.from files([coverageSourceDirs])
    executionData.from files("$buildDir/outputs/code_coverage/debugAndroidTest/connected/coverage.ec")


    doFirst {
        new File("$buildDir/intermediates/app_classes/debug").eachFileRecurse { file ->
            if (file.name.contains('$$')) {
                file.renameTo(file.path.replace('$$', '$'))
            }
        }
    }
    print "bbb"
}

manifest 文件配置如下

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.sample1">
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <instrumentation
        android:handleProfiling="true"
        android:label="CoverageInstrumentation"
        android:name="com.example.sample1.test.JacocoInstrumentation"
        android:targetPackage="com.example.sample1"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

        </activity>
        <activity
            android:name="com.example.sample1.loginActivity"
            android:parentActivityName="com.example.sample1.MainActivity">

            <!-- The meta-data tag is required if you support API level 15 and lower -->
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value="com.example.sample1.MainActivity" />
        </activity>
        <activity android:name="com.example.sample1.zhuceActivity"
            android:parentActivityName="com.example.sample1.MainActivity" >
            <!-- The meta-data tag is required if you support API level 15 and lower -->
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value="com.example.sample1.MainActivity" />
        </activity>

        <activity android:label="InstrumentationActivity"
            android:name="com.example.sample1.test.InstrumentedActivity" />
    </application>

</manifest>

build 后的文件夹路径如下:

点击 jacocoTestReport 后,提示 build 成功,但是不生成 html 结果

另外第二个问题:执行 createDebugCoverageReport 时会重新安装 apk 和覆盖率的安装包,然后,删除安装包。那之前编译安装,gradle installDebug 安装和 adb shell am instrument 命令调起 app 那一步还有什么用了呢??ec 会在执行 createDebugCoverageReport 生成新的 ec 文件,且如果没有 sleep 的话,覆盖率都是 0 啊??

第三个问题:
createDebugCoverageReport 会生成 ec 文件和报告,但是需要一直链接 USB 且本地编译 Android 代码。问题来了:createDebugCoverageReport 生成的报告有数据,这个 createDebugCoverageReport 和 jacocoTestReport 的区别是什么?

最最最困惑的问题就是为什么执行 jacocoTestReport 时,不会生成报告

=====================
给后来的人一个答案吧,问题已经解决了。
问题一:不能生成 html 报告的问题:jacoco 版本替换到 0.8.5,gradle3 的编译后的路径是./build/intermediates/javac/debug,不是之前的 app_classes
问题二:createDebugCoverageReport 是 jacoco 中自带的生成报告和路径的命令,但是报告都是 0,需要替换到自己拉出来的 ec 文件。
问题三:jacocoTestReport 才是我们自己写的生成报告的 task

Shen [该话题已被删除] 中提及了此贴 06月15日 20:23
activity android:label="InstrumentationActivity"    android:name="coverage.netease.com.test.InstrumentedActivity" />

这个写错了吧,name 应该是包名.test.InstrumentedActivity 吧

还有使用真机 adb shell am instrument com.xxx.gotoheipi/com.xxx.gotoheipi.test.JacocoInstrumentation 手机也 root 过 小米 10 完全没反应 如果用模拟器还行

在线调试看看 log

在路上 回复

小米和红米完全不得行 root 过 但是 data 文件夹路径里面没有创建 com.xxx.xx

哦哦,那应该是库方法更新了导致的吧,毕竟这代码两年前的了。
辛苦将替换代码留言一下,我在文章中更新一下,避免误导了后面的同行

在路上 回复

使用了你的配置 构建失败 代码提示这几个方法 classDirectories sourceDirectories executionData 过时

能生产 coverage.ec 了,但是使用上面方法生产报告方法是失败的

看看 logcat 的报错

在路上 回复

我使用 adb shell am instrument 后 app 闪退了

蓝蓝 回复

看看代码?这样说,不知道具体是什么

楼主,我还有个问题呀,android.support.* 这种的怎么屏蔽掉?说是 gradle excludes 不管用导致的?

在路上 回复

可以的,灰常感谢哇!👍

蓝蓝 回复

首先,建议写到 sdcard 中,如果写到包 data 路径下,包被卸载后,.ec 文件会被删除
关于权限问题,可以放到 Cache 路径下,getExternalCacheDir() + "/“,我验证了,安卓 10 也没有权限问题

关于 getExternalCacheDir() 的具体路径信息,可以打印查看

adb pull /data/data/xxx/files/coverage.ec(Permission denied)怎么办,我打印出来在 /data/user/0/xx/files/coverage.ec 下,但是还是 Permission denied,换成/sdcard/下,加了权限,还是 Permission denied😭

这是米 8,安卓 9 系统这样。换个 5.0 手机,pull 文件的时候提示:does not exist

在路上 回复

嗯 找到问题了。。用错模版了 用了 basic 的模版。。

晓只 回复

看日志

执行 adb shell am instrument 启动 app 闪退是啥情况呢?

求助,找了一天没有解决这个问题!!!
在单模块项目里,我按照上面操作成功了。在多模块安装项目里,我把上述文件都放到主模块了,然后使用 adb shell am instrument 出现如下报错:

jiawei0113 回复

这个怎么成本低,怎么来吧

在路上 回复

每次杀进程之前都执行生成 ec 文件,然后我们大概一百多条 case,这样会生成 100 多个.ec 文件?然后再合并?

jiawei0113 回复

杀进程之前,执行覆盖率文件生成逻辑。

simple 回复

求问,我们 Android 自动化是每执行一条 case 就杀进程,杀了进程就没有代码覆盖记录了。如何统计啊?现在全部都做完了发现了这个问题。求助感谢

花开 Android 测试增量和全量覆盖率实践 中提及了此贴 12月06日 16:05

是用的你说的这个,请问如何实现增量覆盖,我查了很多资料,还使用过 diff-cover 工具,还是没实现增量覆盖率

不是,用这个方式配置吧,比 Instrument 方式好,更灵活:https://testerhome.com/articles/17546

InstrumentedActivity 是不是只能继承标识为 android.intent.action.MAIN 的 Activity,不一定是 ManiActivity 对吧?

@zailushang 感谢大神!!

小小南瓜 回复

检查一下这两个路径是否是有效的路径

在路上 回复

是的 ,想看到具体哪些行被覆盖了,但是我的报告只能看到方法级别的覆盖率

小小南瓜 回复

意思是,点进去以后,看不到哪些代码覆盖了,哪些代码没有覆盖吗?

你好 我在查看覆盖率报告时发现报告没有行覆盖率,请问你有遇到过这关问题吗

迷糊群 回复

我没有做自动上传,因为每次的覆盖率文件比较大。
大文件建议用 FTP 上传吧

在路上 回复

自动上传是用的什么方式呢,能说详细一点吗?感谢

迷糊群 回复

不同手机的,可以把不同手机的覆盖率文件下载、合并,然后再计算覆盖率

迷糊群 回复

可以自动上传,什么方式都可以
也可以手动导出
app 重装不重装,跟覆盖率没什么关系吧?本文统计的是代码覆盖率。

请教一下,生成的报告在手机本地可以自动上传吗,如果不能自动上传的话是手工导出来吗,如果 app 重装了,那这个覆盖率的数据不是没有用了?因为基本测试的时候是多个人用不同的手机在测试,如何体现这个版本的 app 的覆盖率呢?

simple [精彩盘点] TesterHome 社区 2018 年 度精华帖 中提及了此贴 01月07日 12:08
在路上 专栏文章:[定制触发条件] jacoco 统计 Android 代码覆盖率 中提及了此贴 01月01日 22:14
xiaqing 回复

从报错信息看,是 jacoco 的类没有找到,在这里直接引入 jacoco 就行,没有明白你为什么从 gitlab 上拉代码

请问从 gitlab 拉下来的代码,出现这个问题是什么原因?

java.lang.ClassNotFoundException: org.jacoco.agent.rt.RT
 java.lang.ClassNotFoundException: org.jacoco.agent.rt.RT
     at java.lang.Class.classForName(Native Method)
     at java.lang.Class.forName(Class.java:400)
     at java.lang.Class.forName(Class.java:326)
     at com.example.test.JacocoInstrumentation.generateCoverageReport(JacocoInstrumentation.java:72)
     at com.example.test.JacocoInstrumentation.onActivityFinished(JacocoInstrumentation.java:94)
     at com.example.test.InstrumentedActivity.onDestroy(InstrumentedActivity.java:22)
     at android.app.Activity.performDestroy(Activity.java:7082)
     at android.app.Instrumentation.callActivityOnDestroy(Instrumentation.java:1154)
     at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:4322)
     at android.app.ActivityThread.handleDestroyActivity(ActivityThread.java:4353)
     at android.app.ActivityThread.-wrap6(ActivityThread.java)
     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1618)
     at android.os.Handler.dispatchMessage(Handler.java:102)
     at android.os.Looper.loop(Looper.java:163)
     at android.app.ActivityThread.main(ActivityThread.java:6385)
     at java.lang.reflect.Method.invoke(Native Method)
     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:904)
     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:794)
安涛 [该话题已被删除] 中提及了此贴 12月21日 16:29
ivy520 [该话题已被删除] 中提及了此贴 12月16日 23:53
ivy520 [该话题已被删除] 中提及了此贴 12月16日 22:55
simple 专栏文章:[精华帖] 社区历年精华帖分类归总 中提及了此贴 12月13日 14:44
小小南瓜 回复

我没用过,但是 jacoco 有个命令行工具可以 merge,

请问大神,多个 *.ec 文件需要合并成一个时要怎么做呢

邮件已发。

simple 回复

thanks

不二家 回复

我找我们团队的专家(VPDong)来帮你解答一下,你俩聊

匿名 · #31 · 2018年12月07日
仅楼主可见

邮件给你什么?

匿名 #86 · 2018年12月07日
不二家 回复

请邮件给我,可以对你提供些帮助😃

simple 回复

简单介绍一下,不需要多复杂的配置。粗略的让我知道有点方向。非常感谢大哥。

不二家 回复

最近在搞绩效,等下周找时间写篇教程哈

@simple 大哥,实在想知道,如何统计以 jar 包引入的 sdk 覆盖率的统计?能多点介绍吗?

@simple 方便增加点描述关于 sdk 覆盖率收集吗?

不二家 回复

可以微信,QQ 一般不登录,TTMMD155

不二家 回复

方便私聊不,想跟你 QQ 一下,872489864 方便不?

在路上 回复

代码应该不行,我还是自己再看看吧,谢谢。

不二家 回复

方便的话,可以把你的代码发过来吗?不然这样不好解决

在路上 回复

我不是小米的,我确认了一遍配置,没有配错。我对这个报错很懵逼啊

我参考这个地址 https://stackoverflow.com/questions/14269687/android-util-androidexception-instrumentation-failed ,然后使用命令
adb shell pm list instrumentation,的确没有看到我安装的应用包名。

不二家 回复

在 manifast 中注册 InstrumentedActivity 了吗? 你是小米的?

在路上 回复

这就是所有报错唉,我在命令行已经安装应用成功,然后用这个命令adb shell am instrument命令调起 app 的时候报错

不二家 回复

能把所有报错都发出来吗?这段看不太出来原因

@simple @zailushang 我在主项目内尝试 jacoco,然后已经安装好 apk,以后然后启动服务的时候报错了:

adb shell am instrument com.xiaomi.aaa/com.xiaomi.aaa.test.JacocoInstrumentation
android.util.AndroidException: INSTRUMENTATION_FAILED: com.xiaomi.aaa/com.xiaomi.aaa.test.JacocoInstrumentation
 at com.android.commands.am.Am.runInstrument(Am.java:956)
 at com.android.commands.am.Am.onRun(Am.java:316)
 at com.android.internal.os.BaseCommand.run(BaseCommand.java:47)
 at com.android.commands.am.Am.main(Am.java:99)
 at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method)
 at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:330)
不二家 回复

我们的 sdk 是插件化多模块的方式,收集覆盖率信息会更麻烦一些,后面可以出一篇文章发到社区里,你试一下先

simple 回复

@zailushang @simple 感谢,我先尝试一下,统计主项目的覆盖率的内容,然后尝试 sdk

不二家 回复


参考一下这个

不二家 回复

@simple @ 不二家的小球迷 simple 兄弟,可以给小球迷详细说说具体步骤吗?我没这么实践过,只是理论上觉得是可以的

在路上 回复

然后呢?在主项目的 build.gradle 中不需要配置 jacoco 的配置吗?能否多讲点大致的步骤?另,我们的 sdk 是以 jar 包形式引入主项目中的。

simple 回复

👍 正解

不二家 回复

把 jacoco 的配置写在 sdk 项目的 gradle 配置文件中,我们的 SDK 就是这样做的

在路上 回复

如果单单只想测 kotlin 写的一个 sdk,但是这个 sdk 在另一个项目 A 中引用,且需要在 A 中测试 sdk 的测试覆盖率,不知道有什么好实现的思路?谢谢。

不二家 回复

kotlin 可以和 java 无缝配合,所以可以做的

手把手教程,如果能有个视频就更好了

恒温 将本帖设为了精华贴 11月25日 08:26

@chenhengjie123 老大,请教一下,kotlin 实现的 Android 应用也能用 Jacoco 实现手工测试覆盖率的收集吗?

西门吹牛 回复

给一个截图或者 log 信息看看吧

请教一个问题,当我把 instrumentation 在 manifest 里面申明以后,运行老报错,说不识别 instrumentation 这个资源,这个是啥问题,如何解决?

需要注意的是:build.gradle 文件中,app_classes 的名字需要和项目本身实际匹配,如果不匹配,会出现 FileNotFoundException

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册