Java 服務端和 Android 端手工測試覆蓋率統計的實現


本文為霍格沃茲測試學院優秀學員關於後端和 App 端手工測試覆蓋率的學習筆記。測試開發進階學習,文末加群。


一、前言

代碼測試覆蓋率工具流行了這麼多年,已經有很多成熟方案比如 Jacoco,我司近一段時間開始了這方面的摸索,很榮幸這個任務到了我的手裡,於是乎就開始踩坑之旅。

之前已經搞定了 Java 後端的覆蓋率統計,由於我們沒有 UT,毫無疑問使用的還是 On-the-fly 模式,最近幾天開始了 App 端的手工測試覆蓋率統計之旅,中間出現了一些坑,有的很快就搞定了,有的也多多少少佔用了不少時間,在此記錄一下,拋磚引玉,大家一起探討。

二、思路整理

2.1 關於 Java 端

我們都知道,Jacoco 流行了這麼多年,無疑是解決 Java 後端覆蓋率的不二之選 (至於 Emma,當然也是先驅,只不過我們沒有選擇)。分析了我司關於 Java 端項目的現狀,總結了如下幾點:

  • 老牌的項目,使用 Java6, 框架就是 ssh ,一般來說都是 Ant 構建,jetty 部署。
  • 稍微新一點的項目,使用 Java8,主框架 SSM,一般直接 maven 構建,maven 的 tomcat 插件部署或者打成 war 包用 tomcat 部署。
  • 再新一點的項目,使用 Java8,就是 Springboot 和 Spring Cloud 框架,maven 的 springboot 插件部署或者 war 包用 tomcat 部署,或者打成 jar 包,直接用 Java 命令啟動。

終其一點,還是要用 On-the-fly 模式,方便。

2.2 關於 Android 端

基本清一色 Android Studio 開發,gradle 構建打包

2.3 關於覆蓋率環境的部署和收集

研究過 Jacoco 的都知道,它支持多種方式的部署和報告生成。但總結到一點,還是修改啟動時候的 JVM 參數,-Javaagent 配置一下,但是不同的構建工具有其封裝方式,Ant 和 Maven 也是 shell 或者 bat 腳本,對一系列操作的封裝,底層依然是 Java 命令的調用。

所以一個萬能的方式,就是修改 Ant 和 mvn 的腳本,直接修改其腳本中用到的 JAVA_OPTS 參數,但是弊端也很明顯,就是會對所有的 Ant 和 mvn 命令生效,因此並不可取。所以,還是選擇了針對特定構建和打包方式的啟動適配。比如:

  • Ant 啟動

可以修改 build.xml,在啟動部署的 target(比如我司是用 startJetty) 中配置一個 jvmarg,設置成需要啟動的 Jacoco 配置,如下所示:

<code><target>
    <mkdir>
    <mkdir>
    <java>
        
        
        
        
        
        <jvmarg>/<java>/<target>/<code>


  • Maven 啟動

mvn 啟動的時候,看過 mvn 腳本的大概知道,他提供了一個 MAVEN_OPTS 這個環境變量,可以臨時修改啟動參數,因此對 mvn tomcat7:run 或者 mvn springboot:run 這種方式部署項目,可以選擇臨時修改它,來完成 Jacoco 的注入,如下所示:

<code>export MAVEN_OPTS="-Javaagent:$JacocoJarPath=includes=*,output=tcpserver,port=2014,address=192.168.110.1"/<code>

之後,在運行 mvn xx:run(需要後臺啟動的話,加上 nohup 也無可厚非),這樣基本可以注入 Jacoco agent 了。

部署之後,再設置:

<code>export MAVEN_OPTS=""/<code>

就可以恢復,或者換個 terminal 窗口,也可以恢復,這樣不用動任何的後端代碼,也不會對當前的服務器環境造成汙染,開發在測試環境部署的時候,有很多會喜歡這種方式。

  • jar 啟動 Ant

這個就更簡單了,因為這個是最原始也是直接的 Java 應用啟動方式

<code>Java -Javaagent: $JacocoJarPath=includes=*,output=tcpserver,port=2014,address=192.168.110.1 -jar  xxxxxxxxxx.jar/<code>

此處需要注意-Javaagent 這個參數的放置位置,因為放在 .jar 包之前是針對 jvm 設置,放到 .jar 之後是針對 jar 包裡啟動的 main 方法 args 的參數,位置放錯,就會注入 Jacoco 失敗。

  • tomcat 啟動

這種方式,就是改變 tomcat 的啟動文件,catalina.sh 或者 catalina.bat 中的 JAVA_OPTS 參數,這個網上文檔有很多,不再多說。

2.4 關於覆蓋率數據的收集和報告生成

這也有很多方式,比如用 Ant 的 build.xml 和 maven 的 Jacoco 插件來收集和生成報告,這個網上也有很多,不再多議。

但其實看過官方文檔的就知道,其實 Jacoco 自己提供了 API,來收集和生成報告,這是比較原始的方式,也是最好用的方式。

Jacoco 給出的 API 示例如下:

Jacoco 官方的 API 示例地址:

https://www.Jacoco.org/Jacoco/trunk/doc/API.html

為了降低測試部對這些知識的學習成本,我選擇了統一的方式,用 API 來收集和生成報告,這樣以來,測試人員需要介入的地方就只剩下了帶有 Jacoco agent 的測試環境部署,剩餘的事情,交給我就行了。

因此,花了點時間做了個覆蓋率的收集和生成報告的平臺,這個後面會簡單說下,不是為了炫耀也不是為了拿績效,就是單純想減少學習成本 (這樣大家就不會抱怨還要學習 Ant 的知識啦、maven 的知識啦、可能還需要學習 Java 的知識啦,雖然這是好的,但是我相信還是有很多人不願意投入的)。

2.5 關於 App 端覆蓋率的收集和報告生成

關於 App 端的覆蓋率收集和報告生成,社區有很多帖子介紹,我稍後也會提到,給我幫助很多。

主要思路,就是先拿到手工測試的覆蓋率數據,因為這裡是用 offline 的方式生成的,這一點好像途徑並不多,但比較麻煩,還是要懂 Android 端的開發知識 (比如 AS 的使用、gradle 的配置和任務執行、Android 工程代碼的結構、甚至還要懂一些 Groovy 的語法等等),因為要涉及到對工程的一部分修改和打包。

拿到覆蓋率數據之後,就可以生成報告了,那按照之前的說法,也有兩種方式:

  • 使用 Gradle 的 Jacoco plugin,它給出了生成報告的任務,這些可以看參考資料。
  • 有了前面的覆蓋率收集和報告生成平臺,既然收集不需要了,那麼報告生成是可以複用的嘛,於是乎做了下對 Gradle 工程的適配,只要上傳 App 端的 exec 文件,就可以生成報告。

我選擇了後者,還是那句話,因為如果用前者,那勢必要給測試人員講解 Android 開發一些相關知識 (生成報告的時候,還要涉及到源碼和 class 文件的配置,以及涉及到多模塊的收集配置)

四、App 端覆蓋率進行時遇到的坑

按照上述的這些精彩文章裡,對 Android 端的代碼覆蓋率統計的介紹裡,照理來說應當一氣呵成了,但還是遇到了一些坑。

比如: 在收集到手機端的覆蓋率數據之後,傳到後臺,開始生成的時候,一直報以下錯誤:

<code>[ERROR] c.a.p.t.j.ReportGenerator - IO 異常 ,Cannot read execution data version 0x1006. This version of Jacoco uses execution data version 0x1007.

[ERROR] c.a.p.e.GlobalExceptionHandler - 

com.administrator.platform.exception.base.BusinessValidationException: 覆蓋率的 Jacoco 版本不匹配:Cannot read execution data version 0x1006. This version of Jacoco uses execution data version 0x1007./<code>

經過分析,這是覆蓋率數據和當前所用生成報告的 Jacoco 版本不一致,我用的是 0.8.1-snapshort 版本,它支持的版本是 0X1007,這個從 Jacoco 的源碼可以看到:

實戰 | Java 服務端和 Android 端手工測試覆蓋率統計的實現

很顯然,是 App 端生成的覆蓋率數據版本低了。

可是按照上面的文檔,Jacoco 中的 toolVersion 已經設置成 0.8.1 了啊,它肯定支持的也是 0X1007 啊。

而且查了官方給出的文檔,對應關係如下:

實戰 | Java 服務端和 Android 端手工測試覆蓋率統計的實現

GitHub 地址在:

https://github.com/Jacoco/Jacoco/wiki/ExecFileVersions

五、填坑經歷

截止到此時,坑已經出現了,那肯定要填,於是乎就開始了一系列排查問題過程:

5.1 首先懷疑的是 toolVersion

於是乎做了以下嘗試

  • 第一步,改版本

往高了調和往低了調都不行,依然是無法解析。

  • 第二步,把這個版本刪除 (設置為空字符串 "")

我去,覆蓋率數據依然能出來,這說明這個 plugin 中設置的這個 toolVersion 對這個 apk 生成的時候不生效啊。

  • 第三步,把那個 testCoverageEnabled 設置成 false

很好,報錯了,ClassNotFound,這就對了,於是乎有了後續的步驟。

5.2 找移動端開發負責人

詢問這個 Jacoco 的機制,結果跟社區的文章裡和網上能查到的大都差不多。

但是他給了另一個提示:“我們這個 Jacoco 插件當前的運行方式是編譯期,而不是運行期”。因為在此之前,我跟他描述了我們的需求,基本確定了我們的生成方式,是運行期而不是編譯期,既然是在 APK 的代碼裡可以用反射查找到,那說明:

Jacoco 的代碼庫,確實被打到了 apk 裡面。

5.3 換 AS 版本

原來用的 181,改成了最新的 193,沒有解決。

5.4. 換 Gradle 版本

原來我們工程內置的是 4.1 版本,換成了其他版本,有的太高了根本無法構建,有的 4.4 4.6 的基本還是同樣的問題。

5.5. 新思路,查依賴的庫

從 task 裡,找到了 dependencies 這個任務,運行了下,發現確實依賴的是 0.7.4.201502262128

<code>```java
|    +--- project :message
|    |    +--- org.Jacoco:org.Jacoco.agent:0.7.4.201502262128
|    |    +--- project :user (*)
|    |    \\--- project :coreLibrary (*)

|    +--- project :highlights
|    |    +--- org.Jacoco:org.Jacoco.agent:0.7.4.201502262128
|    |    +--- project :user (*)
|    |    +--- project :comment (*)
|    |    \\--- project :coreLibrary (*)
|    +--- project :search
|    |    +--- org.Jacoco:org.Jacoco.agent:0.7.4.201502262128

```/<code>

而且解析 apk,發現確實 apk 裡面集成的也是這個版本,如圖所示:

實戰 | Java 服務端和 Android 端手工測試覆蓋率統計的實現

更加確定了這個版本,其實目前來說沒有受我們當前代碼的控制, 繼續從 build.gradle,找到其他依賴的庫,挨個查看,主要找的是根目錄下面的依賴。


在找了數十個依賴都無果之後,讓我發現了這個庫:

<code>    classpath 'com.android.tools.build:gradle:3.0.1/<code>

我們的代碼當前引的是 3.0.1 版本,於是乎在 Maven 中央倉庫中去找了下看看,地址如下:

https://mvnrepository.com/artifact/com.android.tools.build/gradle/3.0.1

它引用了一個編譯期依賴: com.android.tools.build:gradle-core:3.0.1,地址如下:

https://mvnrepository.com/artifact/com.android.tools.build/gradle-core/3.0.1

如下圖所示:

實戰 | Java 服務端和 Android 端手工測試覆蓋率統計的實現

哇哦,發現了新大陸,它裡面引用了 Jacoco 的庫,也確實是 0.7.4.201502262128 版本,後面的事情就沒有任何選擇滴開始了,升級這個版本!!!!

六、升級版本做了什麼

可全局查找該屬性所在位置,本例中,在項目工程下的 build.gradle 聲明 , 修改後如下:

  1. 修改 com.android.tools.build:gradle 版本
<code>dependencies {
        classpath 'com.android.tools.build:gradle:3.2.1'
        ...
    }/<code>
  1. 原 compile 指令換成 API
  2. 原 testCompile 指令換成 testImplementation
  3. 原 provided 指令換成 compileOnly
  4. 原 instrumentTest 換成 androidTest
  5. 在 gradle.properties 文件增加兩個屬性
<code>android.enableD8.desugaring=true
android.useDexArchive=true/<code>

當然,還可能有其他需要適配的地方,這就跟項目有關了,改到這個地方,我的問題已經基本解決了。

七、最後的分析

按照上述的解決問題分析,最終順利生成了報告。

總結了下,其實在這個過程中,我們需要做的地方,最重要的也就是在 debug 版本里打開testCoverageEnabled 這個開關,剩餘的事情,其實根生成報告的方式有關了,如果按照 API 來生成報告,理論上來說,關於 Jacoco 的其他地方,都不需要改動。

  1. Gradle 的這個 Jacoco plugin

肯定是要 apply 這個插件的,這是前提。

  1. testCoverageEnabled

這個屬性,在 debug 版本里,要設置為 true,不然會無法生成。

  1. Jacoco 的 toolVersion

這個版本號的設置,其實隻影響用 task 來生成 Jacoco 的報告,以及在編譯期運行單元測試的時候生效,在你打完 debug 包進入 apk 之後,其實它就不生效了。

  1. 關於對 Jacoco 的配置

在 build.gradle 中對 Jacoco 的配置,其實只是針對 Jacoco 做了任務的擴展,可以讓你改變 Jacoco 插件對 Jacoco 庫引用的一些默認配置。

比如生成報告的時候引用的 Jacoco agent 和 Jacoco Ant ,以及擴展一些其他生成報告的任務,可以修改 class 的文件夾屬性和 src 屬性以及 exec 文件的路徑(這裡理解的比較淺,可能會有誤解,還請大家仔細研究)。

  1. APK 運行期間生成 exec 數據

劃個重點:因為我們是對 API 的運行期做的覆蓋率數據統計,所以主管這個 Jacoco 數據生成的,其實控制在下面這個庫裡

<code>classpath 'com.android.tools.build:gradle:3.2.1'/<code>

這麼看來,之前參考資料裡面介紹的如此順利,也恰好是因為他們用的這個構建版本剛好是支持 0.7.5 以上的 Jacoco core,這好像在 3.1.4(獲取還有再低點的,我沒仔細看) 以後就支持了,我選擇的 3.2.1 內置的好像是 0.7.9+ 的。

八、覆蓋率平臺

其實不能算是平臺,只能說是內部用的一個小工具,有點醜,整體界面如下:

實戰 | Java 服務端和 Android 端手工測試覆蓋率統計的實現

覆蓋率統計列表界面:

實戰 | Java 服務端和 Android 端手工測試覆蓋率統計的實現

覆蓋率信息配置界面:

實戰 | Java 服務端和 Android 端手工測試覆蓋率統計的實現

最後感謝在摸索的過程中,參考了很多有價值的文章,感謝指引,也歡迎大家一起探討。

References

[1] Android 手工測試的代碼覆蓋率: https://testerhome.com/topics/2510[2] 淺談代碼覆蓋率: https://testerhome.com/articles/16981[3] Android 手工測試代碼覆蓋率增強版: https://testerhome.com/topics/2524[4] Jacoco 統計 Android 代碼覆蓋率 [instrument 方式]: https://testerhome.com/topics/16376[5] Android Jacoco 代碼覆蓋率測試入門: https://testerhome.com/topics/17066[6] 定製觸發條件| Jacoco 統計 Android 代碼覆蓋率: https://testerhome.com/articles/17546[7] AngryTester: https://github.com/AngryTester/Jacoco



分享到:


相關文章: