這點和 JUnit 的 Configuration 不同:
產出 Coverage Report 的第一步
以上的限制,對認真撰寫測試程式的人來說,會造成很大的不便。Code Coverage 是撰寫測試程式的一項重要指標,沒有了 Code Coverage 就如同在伸手不見五指的黑暗中行走,完全不知身在何處,有關更多 Code Coverage 用途的細節,請參考先前的這篇文章。不過,說是做了半套,代表並不是完全沒有辦法產生 Coverage Report。要在 Espresso 測試執行的過程中產生 Coverage Report,要先調整 build.gradle,在 buildTypes 設定中開啟 testCoverageEnabled 選項。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
android { | |
buildTypes { | |
debug { | |
testCoverageEnabled = true | |
} | |
} | |
} |
完成以上修改,並執行 ./gradlew createDebugCoverageReport 指令成功之後,就可以在 Module 的 /build/reports/coverage/debug/ 路徑下,以 Browser 開啟 index.html。
以上的步驟在搜尋之後,都可以取到一堆的文件提供相關的操作細節。但本篇到這裡還沒有結束,這個方法僅適用於測試和待測對象在同一個 Module 中。如果是在不同的 Module 裡,由 Coverage 的 Report 頁面中,其實可以看出清單中的 Package 都只列出測試程式所在 Module 的 Class。
擴增 Coverage Report 的範圍
假設有一個如下所示的專案結構:+ Sample + app + src + androidTest + main + com.sample.app + test + domain + src + main + com.sample.domain
其中 app 是 Android Application,domain 是 Android Library。如果把所有的測試 Class 放在 app 的 Module 之下,在 test 路徑下的測試可經由 Configuration 的 Code Coverage 畫面,來把 domain 中的 Class 列入 Report 產生的範圍。
在 androidTest 中的測試就沒有這麼直接了,以 Android 的 Gradle Plugin 官方文件內容來看,目前並沒有相關可調整的參數。所以這個階段的目標,就是讓 androidTest 產出的 Report 和 test 一樣,可以把指定位置的 Class 列入 Coverage 的範圍內。
要達到這個目標,首先要引入 JaCoCo 的 Plugin,透過 JaCoCo 的參數調整來突破原本的限制。在 JaCoCo 中有 sourceDirectories 和 classDirectories 二項參數,用來指定 Report 要涵蓋的範圍。以上面專案結構的例子來說,需要產生以下的內容在 app 的 build.gradle 中:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
apply plugin: 'jacoco' | |
task jacocoTestReport(type: JacocoReport, dependsOn: "createDebugCoverageReport") { | |
group = "Reporting" | |
description = "Generate Jacoco coverage reports." | |
reports { | |
xml.enabled = false | |
html.enabled = true | |
} | |
def classFilter = ['**/R.class', | |
'**/R$*.class', | |
'**/BuildConfig.*', | |
'**/Manifest*.*', | |
'**/*Test*.*', | |
'android/**/*.*'] | |
classDirectories = files(["${project.buildDir}/intermediates/classes/debug", | |
"${project(:domain).buildDir}/intermediates/classes/debug") | |
sourceDirectories = files(["${project.projectDir}/src/main/java", | |
"${project(:domain).projectDir}/src/main/java"]) | |
executionData = fileTree(dir: "${project.buildDir}", | |
includes: "**/*.ec") | |
afterEvaluate { | |
classDirectories = files( classDirectories.files.collect { | |
fileTree(dir: it, exclude: classFilter) | |
}) | |
} | |
} |
在這裡附帶說明一下,其實可以把以上的內容獨立成單獨的檔案,例如:jacoco.gradle,再透過 apply from: 'jacoco.gradle' 的方式引用。一來可以簡化 build.gradle 的內容、方便維護,二來可以避免調整 JaCoCo 的選項時,IDE 頻繁地出現要求同步的訊息。
另外有一點需要注意的是,createDebugCoverageReport 產出的 executionData 是以 *.ec 為名稱,與一般 JaCoCo 使用的 *.exec 不同。
設定好以上的內容之後,就可以執行 ./gradlew app:jacocoTestReport。和 createDebugCoverageReport 一樣會產出 html 格式的 Report,但不同的是 index.html 會在 app 的 /build/reports/jacoco/jacocoTestReport/html 路徑下看到。這個路徑是預設的,可以透過參數調整。
修正 Coverage Report 的問題
到這裡所有的問題都解決了嗎?其實並沒有!照著以上的內容所產出的 Report 中,domain 下的 Class 不論測試怎麼做,在 Instruction 和 Branch 的 Coverage 都呈現 0%,所以顯然有一個環節出現了問題。花了一番功夫發現 domain 在執行 jacocoTestReport 時都只會產生 release 的 Class。
就算是把 classDirectories 的路徑調整成 release,在產出的 Report 中 Coverage 依然是 0%。
目前解決方案是要調整 domain 的引用方式:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
dependencies { | |
debugCompile project(path: ':domain', configuration: 'debug') | |
releaseCompile project(path: ':domain', configuration: 'release') | |
} |
而在 domain 的 defaultConfig 下也要增加以下的設定:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
android { | |
defaultConfig { | |
publishNonDefault true | |
} | |
} |
使用以上內容再執行一次 ./gradlew app:jacocoTestReport,就可以由 Report 中看到,domain 下的 Class 不再全都是 0% 的狀態。
仍需改善的部份
雖然 Report 已經可以順利的產出,但是與 JUnit 可以在 IDE 中直接檢視 Coverage 情況的經驗相比,還是有差別。畢竟沒有辦法直接在 IDE 對照著 Code 就可以了解 Coverage 的情況,要在 IDE 與 Browser 間來回地切換其實非常地不方便。原本有試著把 createDebugCoverageReport 產出的 executionData,經由以下【Analyze -> Show Coverage Data...】功能所顯示的 Window 載入。但這個功能似乎只能載入 *.exec 的檔案,就算是把 *.ec 改為 *.exec,載入之後 Coverage 的狀態也都呈現 0% 的結果。可能是真的有格式上相容的問題,所以才會把產出的 executionData 以 *.ec 來區格,避免被 Android Studio 載入。
只能期待未來 Android Studio 在改版時,能將這一部份的功能納進入,既然在 Plugin 都已經可以產出 Report 了,把 Plugin 的動作整合到 IDE 的 Coverage 按鈕上,應該不是什麼難事吧!
0 意見:
張貼留言