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 = "\r\n";
 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/目錄下的覆蓋率報告拷貝到展現的位置

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM