• jacoco统计Android手工测试覆盖率并自动上报服务器


    改进了几个点

    1. 不用借助Instrumentation启动,正常启动即可;

    2. 测试代码不用push到主分支,主分支代码拉到本地后用git apply patch方式合并覆盖率代码;

    3. 测试完成后,连按两次back键把app置于后台,并自动上报覆盖率文件到服务器;

    1. 新增覆盖率代码

    src下新建一个test package,放入下面两个测试类

     1 import android.util.Log;
     2 
     3 import java.io.File;
     4 import java.io.FileOutputStream;
     5 import java.io.IOException;
     6 import java.io.OutputStream;
     7 
     8 /**
     9  * Created by sun on 17/7/4.
    10  */
    11 
    12 public class JacocoUtils {
    13     static String TAG = "JacocoUtils";
    14 
    15     //ec文件的路径
    16     private static String DEFAULT_COVERAGE_FILE_PATH = "/mnt/sdcard/coverage.ec";
    17 
    18     /**
    19      * 生成ec文件
    20      *
    21      * @param isNew 是否重新创建ec文件
    22      */
    23     public static void generateEcFile(boolean isNew) {
    24 //        String DEFAULT_COVERAGE_FILE_PATH = NLog.getContext().getFilesDir().getPath().toString() + "/coverage.ec";
    25         Log.d(TAG, "生成覆盖率文件: " + DEFAULT_COVERAGE_FILE_PATH);
    26         OutputStream out = null;
    27         File mCoverageFilePath = new File(DEFAULT_COVERAGE_FILE_PATH);
    28         try {
    29             if (isNew && mCoverageFilePath.exists()) {
    30                 Log.d(TAG, "JacocoUtils_generateEcFile: 清除旧的ec文件");
    31                 mCoverageFilePath.delete();
    32             }
    33             if (!mCoverageFilePath.exists()) {
    34                 mCoverageFilePath.createNewFile();
    35             }
    36             out = new FileOutputStream(mCoverageFilePath.getPath(), true);
    37 
    38             Object agent = Class.forName("org.jacoco.agent.rt.RT")
    39                     .getMethod("getAgent")
    40                     .invoke(null);
    41 
    42             out.write((byte[]) agent.getClass().getMethod("getExecutionData", boolean.class)
    43                     .invoke(agent, false));
    44 
    45             // ec文件自动上报到服务器
    46             UploadService uploadService = new UploadService(mCoverageFilePath);
    47             uploadService.start();
    48         } catch (Exception e) {
    49             Log.e(TAG, "generateEcFile: " + e.getMessage());
    50         } finally {
    51             if (out == null)
    52                 return;
    53             try {
    54                 out.close();
    55             } catch (IOException e) {
    56                 e.printStackTrace();
    57             }
    58         }
    59     }
    60 }

    上传ec文件和设计信息到服务器

      1 import java.io.DataOutputStream;
      2 import java.io.File;
      3 import java.io.FileInputStream;
      4 import java.io.IOException;
      5 import java.io.InputStream;
      6 import java.net.HttpURLConnection;
      7 import java.net.URL;
      8 import java.text.SimpleDateFormat;
      9 import java.util.Calendar;
     10 import java.util.HashMap;
     11 import java.util.Map;
     12 
     13 import android.util.Log;
     14 
     15 import com.x.x.x.LuojiLabApplication;
     16 import com.x.x.x.DeviceUtils;
     17 
     18 /**
     19  * Created by sun on 17/7/4.
     20  */
     21 
     22 public class UploadService extends Thread{
     23 
     24     private File file;
     25     public UploadService(File file) {
     26         this.file = file;
     27     }
     28 
     29     public void run() {
     30         Log.i("UploadService", "initCoverageInfo");
     31         // 当前时间
     32         SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
     33         Calendar cal = Calendar.getInstance();
     34         String create_time = format.format(cal.getTime()).substring(0,19);
     35 
     36         // 系统版本
     37         String os_version = DeviceUtils.getSystemVersion();
     38 
     39         // 系统机型
     40         String device_name = DeviceUtils.getDeviceType();
     41 
     42         // 应用版本
     43         String app_version = DeviceUtils.getAppVersionName(LuojiLabApplication.getInstance());
     44 
     45         // 环境
     46         String context = "";
     47 
     48         Map<String, String> params = new HashMap<String, String>();
     49         params.put("os_version", os_version);
     50         params.put("device_name", device_name);
     51         params.put("app_version", app_version);
     52         params.put("create_time", create_time);
     53 
     54         try {
     55             post("http://x.x.x.x:8888/importCodeCoverage!upload", params, file);
     56         } catch (IOException e) {
     57             e.printStackTrace();
     58         }
     59 
     60     }
     61 
     62     /**
     63      * 通过拼接的方式构造请求内容,实现参数传输以及文件传输
     64      *
     65      * @param url    Service net address
     66      * @param params text content
     67      * @param files  pictures
     68      * @return String result of Service response
     69      * @throws IOException
     70      */
     71     public static String post(String url, Map<String, String> params, File files)
     72             throws IOException {
     73         String BOUNDARY = java.util.UUID.randomUUID().toString();
     74         String PREFIX = "--", LINEND = "
    ";
     75         String MULTIPART_FROM_DATA = "multipart/form-data";
     76         String CHARSET = "UTF-8";
     77 
     78 
     79         Log.i("UploadService", url);
     80         URL uri = new URL(url);
     81         HttpURLConnection conn = (HttpURLConnection) uri.openConnection();
     82         conn.setReadTimeout(10 * 1000); // 缓存的最长时间
     83         conn.setDoInput(true);// 允许输入
     84         conn.setDoOutput(true);// 允许输出
     85         conn.setUseCaches(false); // 不允许使用缓存
     86         conn.setRequestMethod("POST");
     87         conn.setRequestProperty("connection", "keep-alive");
     88         conn.setRequestProperty("Charsert", "UTF-8");
     89         conn.setRequestProperty("Content-Type", MULTIPART_FROM_DATA + ";boundary=" + BOUNDARY);
     90 
     91         // 首先组拼文本类型的参数
     92         StringBuilder sb = new StringBuilder();
     93         for (Map.Entry<String, String> entry : params.entrySet()) {
     94             sb.append(PREFIX);
     95             sb.append(BOUNDARY);
     96             sb.append(LINEND);
     97             sb.append("Content-Disposition: form-data; name="" + entry.getKey() + """ + LINEND);
     98             sb.append("Content-Type: text/plain; charset=" + CHARSET + LINEND);
     99             sb.append("Content-Transfer-Encoding: 8bit" + LINEND);
    100             sb.append(LINEND);
    101             sb.append(entry.getValue());
    102             sb.append(LINEND);
    103         }
    104 
    105 
    106         DataOutputStream outStream = new DataOutputStream(conn.getOutputStream());
    107         outStream.write(sb.toString().getBytes());
    108         // 发送文件数据
    109         if (files != null) {
    110             StringBuilder sb1 = new StringBuilder();
    111             sb1.append(PREFIX);
    112             sb1.append(BOUNDARY);
    113             sb1.append(LINEND);
    114             sb1.append("Content-Disposition: form-data; name="uploadfile"; filename=""
    115                     + files.getName() + """ + LINEND);
    116             sb1.append("Content-Type: application/octet-stream; charset=" + CHARSET + LINEND);
    117             sb1.append(LINEND);
    118             outStream.write(sb1.toString().getBytes());
    119 
    120 
    121             InputStream is = new FileInputStream(files);
    122             byte[] buffer = new byte[1024];
    123             int len = 0;
    124             while ((len = is.read(buffer)) != -1) {
    125                 outStream.write(buffer, 0, len);
    126             }
    127 
    128             is.close();
    129             outStream.write(LINEND.getBytes());
    130         }
    131 
    132 
    133         // 请求结束标志
    134         byte[] end_data = (PREFIX + BOUNDARY + PREFIX + LINEND).getBytes();
    135         outStream.write(end_data);
    136         outStream.flush();
    137         // 得到响应码
    138         int res = conn.getResponseCode();
    139         Log.i("UploadService", String.valueOf(res));
    140         InputStream in = conn.getInputStream();
    141         StringBuilder sb2 = new StringBuilder();
    142         if (res == 200) {
    143             int ch;
    144             while ((ch = in.read()) != -1) {
    145                 sb2.append((char) ch);
    146             }
    147         }
    148         outStream.close();
    149         conn.disconnect();
    150         return sb2.toString();
    151     }
    152 }

    在build.gradle新增

    apply plugin: 'jacoco'
    
    jacoco {
        toolVersion = '0.7.9'
    }
    buildTypes {
        release {
         // 在release下统计覆盖率信息 testCoverageEnabled
    = true } }

    最重要的一行代码,加在监听设备按键的地方,如果连续2次点击设备back键,app已置于后台,则调用生成覆盖率方法。

    1 @Override
    2 public boolean onKeyDown(int keyCode, KeyEvent event) {
    3     if (keyCode == KeyEvent.KEYCODE_BACK) {
    4         ....
    5 
    6         JacocoUtils.generateEcFile(true);
    7     }
    8 
    9 }

    2. git apply patch

    为了不影响工程代码,我这里用git apply patch的方式应用的上面的覆盖率代码

    首先git commit上面的覆盖率代码

    然后git log查看commit

    我提交覆盖率代码的commit是最近的一次,然后拿到上一次的commit,并生成patch文件,-o是输出目录

    git format-patch 0e4c................... -o ~/Documents/jk/script/

    然后使用Jenkins自动打包,拉取最新代码后,在编译前Execute shell自动执行下面的命令,把覆盖率文件应用到工程内

    git apply --reject ~/Documents/jk/script/0001-patch.patch

    执行成功后的输出:

    3. 服务器生成jacoco覆盖率报告

    在服务器我也拉了一个Android工程,专门用于生成报告

    主要在build.gradle新增

     1 def coverageSourceDirs = [
     2         '../app/src/main/java'
     3 ]
     4 
     5 task jacocoTestReport(type: JacocoReport) {
     6     group = "Reporting"
     7     description = "Generate Jacoco coverage reports after running tests."
     8     reports {
     9         xml.enabled = true
    10         html.enabled = true
    11     }
    12     classDirectories = fileTree(
    13             dir: './build/intermediates/classes/debug',
    14             excludes: ['**/R*.class',
    15                        '**/*$InjectAdapter.class',
    16                        '**/*$ModuleAdapter.class',
    17                        '**/*$ViewInjector*.class'
    18             ])
    19     sourceDirectories = files(coverageSourceDirs)
    20     executionData = files("$buildDir/outputs/code-coverage/connected/coverage.ec")
    21 
    22     doFirst {
    23         new File("$buildDir/intermediates/classes/").eachFileRecurse { file ->
    24             if (file.name.contains('$$')) {
    25                 file.renameTo(file.path.replace('$$', '$'))
    26             }
    27         }
    28     }
    29 }

    然后设备上传ec文件到Android工程的$buildDir/outputs/code-coverage/connected目录下,并依次执行

    gradle createDebugCoverageReport
    gradle jacocoTestReport

    最后把$buildDir/reports/jacoco/目录下的覆盖率报告拷贝到展现的位置

  • 相关阅读:
    @hdu
    @51nod
    @51nod
    @51nod
    JS-正则表达式常规运用
    CSS-复选框默认样式修改
    Vue-路由传参query与params
    Vue-阻止页面回退
    Vue-表单提交
    JS-原生的ajax
  • 原文地址:https://www.cnblogs.com/xiaoluosun/p/7234606.html
Copyright © 2020-2023  润新知