安卓app功能或自動化測試覆蓋率統計(不用instrumentation啟動app)

北漂的雷子發表於2020-10-06

   一文帶你揭祕如何採取非instrumentation啟動app,打造實時統計覆蓋率,一鍵觸發覆蓋率測試報告。


 

  在上篇文章,一文帶你解決Android app手工測試或者自動化測試覆蓋率統計(擼程式碼版),我們採用了instrumentation的方式去啟動app,很多人會問,如果我們不用instrumentation啟動app的方式,正常啟動app進行測試,然後收集覆蓋率可以嗎,答案,是可以的,如何做呢,下面帶你去揭曉其中的奧祕。

        首先呢,我們還是基於我們的工作,去申請我們的讀寫的許可權。

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

  

申請後,我們在安裝app的時候一定要給予這兩個許可權,接下來呢,我們去配置jacoco相關的。

在專案的build.gradle配置相關的,如下配置

apply plugin: 'jacoco'
jacoco {    
toolVersion = "0.8.4"    
description("$buildDir/coverage.exec")   
 reportsDir = file("$buildDir/reports/jacoco")
}

  

        首先我們去新增jacoco的外掛,接著呢,我們去規定版本,然後去規定我們的覆蓋檔案的位置,接下來我們去告知下測試報告的位置。這樣我們配置好了依賴,我們需要在debug開啟覆蓋率。還是同一個的build.gradle配置

 debug {           
/**開啟覆蓋率統計開關*/
testCoverageEnabled = true
minifyEnabled false //獲取程式碼覆蓋率需要設為false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' }

  

配置完畢後呢,我們去編寫一個jacoco的工具類,用來處理覆蓋率檔案的寫入。具體程式碼如下
package com.example.studayapp.test;

import android.content.Context;
import android.os.Environment;
import android.util.Log;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class JacocoUtils {
    static String TAG = "JacocoUtils";

    private static String DEFAULT_COVERAGE_FILE_PATH = "coverage.exec";

    /**
     * 生成ec檔案
     *
     * @param isnew 是否重新建立exec檔案
     */
    public static void generateEcFile(boolean isnew) {
        Log.d(TAG, "生成覆蓋率檔案: " + DEFAULT_COVERAGE_FILE_PATH);
        OutputStream out = null;
        File mCoverageFilePath = new File(Environment.getExternalStorageDirectory(),DEFAULT_COVERAGE_FILE_PATH);

        try {
            if (isnew && mCoverageFilePath.exists()) {
                Log.d(TAG, "清除舊的exec檔案");
                mCoverageFilePath.delete();
            }
            if (!mCoverageFilePath.exists()) {
                mCoverageFilePath.createNewFile();
            }
            out = new FileOutputStream(mCoverageFilePath.getPath(), true);

            //反射:獲取org.jacoco.agent.rt.IAgent
            Object agent = Class.forName("org.jacoco.agent.rt.RT")
                    .getMethod("getAgent")
                    .invoke(null);

            //反射:getExecutionData(boolean reset),獲取當前執行資料,
            // 以jacoco二進位制格式轉儲當前執行資料
            // getExecutionData(boolean reset),reset如果為true,則之後清除當前執行資料
            out.write((byte[]) agent.getClass().getMethod("getExecutionData", boolean.class)
                    .invoke(agent, false));

        } catch (Exception e) {
            Log.e(TAG, "generateEcFile: " + e.getMessage());
        } finally {
            if (out == null)
                return;
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

  


        我們通過反射:獲取org.jacoco.agent.rt.IAgent,然後,反射:getExecutionData(boolean reset),獲取當前執行資料,最後寫入執行的資料。

        這樣我們收集的資料的指令碼下好呢,那麼我們應該怎麼去收集我們的資料呢,之前的文章是通過系統的返回鍵後去生成的,這樣呢,其實在我們實際的工作中呢,是不常見呢,很多的時候呢,我們需要在特定的時候才去觸發呢,這裡呢,我的做法呢,是在設定中,增加一個按鈕,生成測試覆蓋率的 按鈕來統一處理。

        

<Button                    
android:id="@+id/statistics"
android:layout_width="match_parent" android:layout_height="wrap_content" android:text="統計覆蓋率">
</Button>

          在佈局檔案呢,我們去建立一個按鈕,然後呢,我們去在這個按鈕去監聽點選事件。

    

statistics=(Button) findViewById(R.id.statistics);
        statistics.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                JacocoUtils.generateEcFile(true);
            }
        });

  

    這樣呢,我們去安裝我們的app的debug版本

 

    然後呢,我們去正常測試,最後呢,我們去點選我們的按鈕。生成完畢後,如下。

 

 

 

我們去在專案的目錄下,我們去pull下來即可。

adb pull /sdcard/coverage.exec .

     然後,我們在build.gradle建立一個任務。

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/javac/debug/classes',
            excludes: ['**/R*.class',

                       '**/*$InjectAdapter.class',
                       '**/*$ModuleAdapter.class',
                       '**/*$ViewInjector*.class'
            ])
    sourceDirectories = files(coverageSourceDirs)
    executionData = files("$buildDir/coverage.exec")

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

  然後點選

 

 

 

執行完畢後。

 

 

 

我們看下實際的效果

 

 

 

可以看到有測試報告,我們開啟看下。

 

 

 

這是最後的覆蓋率測試的統計資料。

 

        這裡的資料呢,只是統計到了全量程式碼的,還有增量程式碼覆蓋率統計,多個覆蓋率檔案的不同的如何進行組合。後續的文章會持續分享。

 

 

 

 

相關文章