2017/6/12

Espresso 只做了半套的 Code Coverage

有在使用 Espresso 撰寫測試程式的人應該都知道,在 Android Studio 中 Android Test 類型的 Configuration 是不能使用 Code Coverage 的,最少在 Android Studio 2.2.2 仍然是如此。也就是說「Run 'xxx' with Coverage」的按鈕沒有辦法按,情況如下圖所示:


這點和 JUnit 的 Configuration 不同:



產出 Coverage Report 的第一步

以上的限制,對認真撰寫測試程式的人來說,會造成很大的不便。Code Coverage 是撰寫測試程式的一項重要指標,沒有了 Code Coverage 就如同在伸手不見五指的黑暗中行走,完全不知身在何處,有關更多 Code Coverage 用途的細節,請參考先前的這篇文章

不過,說是做了半套,代表並不是完全沒有辦法產生 Coverage Report。要在 Espresso 測試執行的過程中產生 Coverage Report,要先調整 build.gradle,在 buildTypes 設定中開啟 testCoverageEnabled 選項。

完成以上修改,並執行 ./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 中有 sourceDirectoriesclassDirectories 二項參數,用來指定 Report 要涵蓋的範圍。以上面專案結構的例子來說,需要產生以下的內容在 app 的 build.gradle 中:

在這裡附帶說明一下,其實可以把以上的內容獨立成單獨的檔案,例如: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 的引用方式:

而在 domain 的 defaultConfig 下也要增加以下的設定:

使用以上內容再執行一次 ./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 按鈕上,應該不是什麼難事吧!
















2017/6/5

解決 Spock 與 PowerMock 的整合問題

在前一篇文章中,提到了如何在 Spock 中測試 Static 的 Method,以彌補 Spock 在這個部份的不足。當時使用的是 PowerMock 1.6.2,只不過隨著時間的推移,最新的 Mockito 與 PowerMock 組合,在與 Spock 的整合上並不順利。

Mockito 目前已經發展到第二版,但是要在這個版本的 Mockito 上使用 PowerMock,依據官方的說明仍然還停留在試驗性質的版本,目前最新可取得的版本是 1.7.0RC4。

如果使用前一篇文章提到的,以 @Rule 的方式來啟動 PowerMock:

原本可以順利執行的測試案例,在執行測試時會出現 NullPointerException

這個問題在網路上的資訊不多,如果無法解決,就要回到 JUnit + Mockito + PowerMock 的方案,原本 Spock 所帶來的優勢就失去了,是一個很兩難的抉擇。

所幸在研讀 PowerMock 的官方文件時,提到了一個新的功能。在 PowerMock 1.6.0 開始,PowerMockRunner 可以 Delegate 另一個 JUnit Runner,以取代無法使用 JUnit Rule 的情況。

而在 Specification 的 Source Code 中可以看到,Spock 是使用名為 Sputnik 的 JUnit Runner。

所以把之前的 Code 改成以下的內容:

build.gradle 可以更精簡:

透過 PowerMockRunner 的 Delegation,在 Spock 1.1 及 1.0 上,基本的功能都可以正常的運作,只不過會有以下額外的訊息出現。

Notifications are not supported for behaviour ALL_TESTINSTANCES_ARE_CREATED_FIRST
Notifications are not supported when all test-instances are created first!

由於不是 Production 的 Code,所以只要能運作,有點訊息或是使用的是非正式的版本,都還在可接受的範圍內。