JaCoCo測試代碼覆蓋率

一.背景介紹
在產品客戶端的測試過程中,新功能測試以及迴歸測試在手工測試的情況下,即便是測試用例再詳盡,難免也可能會有疏漏之處。故使用代碼覆蓋率工具Jacoco作爲手工測試代碼覆蓋率的統計.

解決的問題: 通過查看測試代碼覆蓋率客觀數據,來進一步完善業務測試場景,完善測試用例.

二.使用方法
1. 由於Android本身集成了Jacoco工具,故在其他工程如果需要生成代碼測試覆蓋率,則僅需要添加如下配置

buildTypes {
    debug {
        testCoverageEnabled true
        ... ...
    }
}

2. 如果針對工程添加配置,在build apk時出現死循環鎖( Waiting to acquire shared lock on daemon addresses registry),針對此種情況,需要增加如下配置:

configurations.all {
    resolutionStrategy {
        force 'org.jacoco:org.jacoco.report:0.7.4.201502262128'
        force 'org.jacoco:org.jacoco.core:0.7.4.201502262128'
    }
}

app/build.gradle 增加配置(僅僅針對build不成功工程)

3. project/app/build.gradle 增加配置,此配置用於生成代碼覆蓋率報告

/*Java Code Coverage*/
apply plugin: 'jacoco'
 
def coverageSourceDirs = [
        '../src/com/i/coverage'
]
 
task jacocoTestReport(type: JacocoReport) {
    group = "Reporting"
    description = "Generate Jacoco coverage reports"
    additionalSourceDirs = files(coverageSourceDirs)
    sourceDirectories = files(coverageSourceDirs)
 
    reports {
        xml.enabled = true
        html.enabled = true
    }
 
    classDirectories = fileTree(
            dir: './build/intermediates/classes/normal/debug',
            excludes: ['**/R.class', '**/R$*.class', '**/*$ViewInjector*.*', '**/BuildConfig.*', '**/Manifest*.*']
    )
 
    executionData = files('./build/outputs/code-coverage/connected/flavors/NORMAL/coverage.ec')
}

(1) 生成測試報告需要添加插件jacoco
(2) 針對build不成功工程其jacoco版本必須和2步驟對應,否則會造成報告不能讀
(3) coverageSourceDirs爲測試代碼路徑,executionData爲生成測試報告的數據源路徑

4. 工程配置以及測試工具apk
(1) 測試工程添加廣播接收者

 <receiver android:name="test.JacocoReceiver">
      <intent-filter>
          <action android:name="jacoco.test.action.START"/>
          <action android:name="jacoco.test.action.CLEAR"/>
          <action android:name="jacoco.test.action.END"/>
      </intent-filter>
</receiver>

public class JacocoReceiver extends BroadcastReceiver {
    private static String TAG = "JacocoReceiver:";
    private final static String ACTION_START = "jacoco.test.action.START";
    private final static String ACTION_END = "jacoco.test.action.END";
    private final static String ACTION_CLEAR = "jacoco.test.action.CLEAR";
    private static String mCoverageFilePath;
    private Context mContext;

    @Override
    public void onReceive(Context context, Intent intent) {
        mContext = context;
        if (intent != null) {
            String aciton = intent.getAction();
            if (ACTION_START.equals(aciton)) {
                createFile();
            } else if (ACTION_END.equals(aciton)) {
                generateCoverageReport();
            } else if (ACTION_CLEAR.equals(aciton)) {
                deleteCoverageReport();
            }
        }

    }

    private void createFile() {
        mCoverageFilePath = mContext.getFilesDir().getPath() + File.separator + "coverage.ec";
        File file = new File(mCoverageFilePath);
        if (!file.exists()) {
            try {
                file.createNewFile();
                Log.i(TAG, "create report");
            } catch (IOException e) {
                Log.e(TAG, "IOException", e);
            }
        }
    }

    private void generateCoverageReport() {
        Log.i(TAG, "generateCoverageReport():" + mCoverageFilePath);
        OutputStream out = null;
        try {
            out = new FileOutputStream(mCoverageFilePath, 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.e(TAG, "Exception", e);
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    Log.e(TAG, "IOException", e);
                }
            }
            Log.i(TAG, "jacoco end");
        }
    }
    
    private void deleteCoverageReport() {
        if (!TextUtils.isEmpty(mCoverageFilePath)) {
            File file = new File(mCoverageFilePath);
            if (file != null && file.exists() && file.isFile()) {
                file.delete();
                Log.i(TAG, "delete report");
            }
        }
    }
}

(2) 測試工具發送廣播,然後手動操作測試

public class MainActivity extends Activity implements View.OnClickListener {
    private final static String ACTION_START = "jacoco.test.action.START";
    private final static String ACTION_END = "jacoco.test.action.END";
    private final static String ACTION_CLEAR = "jacoco.test.action.CLEAR";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }

    private void initView() {
        findViewById(R.id.start).setOnClickListener(this);
        findViewById(R.id.end).setOnClickListener(this);
        findViewById(R.id.clear).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.start:
                Intent intent = new Intent(ACTION_START);
                sendBroadcast(intent);
                Toast.makeText(this, "start listener coverage",1).show();
                break;
            case R.id.end:
                Intent intentEnd = new Intent(ACTION_END);
                sendBroadcast(intentEnd);
                Toast.makeText(this, "create coverage report end",1).show();
                break;
            case R.id.clear:
                Intent intentClear = new Intent(ACTION_CLEAR);
                sendBroadcast(intentClear);
                Toast.makeText(this, "clear coverage report success",1).show();
                break;
            default:
                break;
        }

    }
}

(1) 測試工具用於生成測試代碼數據源,在測試代碼覆蓋率時需要使用,生成數據源,路徑/data/data/{packagename}/files/coverage.ec;
(2) 生成測試代碼覆蓋率報告時,需要拷貝該數據源到{projectname}/app/build/outputs/code-coverage/connected/flavors/NORMAL/coverage.ec
(3) 執行步驟3的task jacocoTestReport , 則在{projectname}/app/build/reports/jacoco/jacocoTestReport/html 生成測試報告,可針對性的查看.

三.報告樣例
在這裏插入圖片描述
四.後續
以上是全量代碼覆蓋率的生成,適用於大改版的情況下去做全面的測試覆蓋;
在日常的小版本迭代過程中,適合使用差量代碼覆蓋率的使用.
//TODO 差量代碼覆蓋率 https://testerhome.com/topics/5823

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章