1. 程式人生 > >聊聊Go程式碼覆蓋率技術與最佳實踐

聊聊Go程式碼覆蓋率技術與最佳實踐

> "聊點乾貨" ## 覆蓋率技術基礎 截止到Go1.15.2以前,關於覆蓋率技術底層實現,以下知識點您應該知道: * go語言採用的是插樁原始碼的形式,而不是待二進位制執行時再去設定breakpoints。這就導致了當前go的測試覆蓋率收集技術,一定是侵入式的,會修改目標程式原始碼。曾經有同學會問,插過樁的二進位制能不能放到線上,所以建議最好不要。 * 到底什麼是"插樁"?這個問題很關鍵。大家可以任意找一個go檔案,試試命令`go tool cover -mode=count -var=CoverageVariableName xxxx.go`,看看輸出的檔案是什麼? * 筆者以這個檔案為例`https://github.com/qiniu/goc/blob/master/goc.go`, 得到以下結果: ```go package main import "github.com/qiniu/goc/cmd" func main() {CoverageVariableName.Count[0]++; cmd.Execute() } var CoverageVariableName = struct { Count [1]uint32 Pos [3 * 1]uint32 NumStmt [1]uint16 } { Pos: [3 * 1]uint32{ 21, 23, 0x2000d, // [0] }, NumStmt: [1]uint16{ 1, // 0 }, } ``` 可以看到,執行完之後,原始碼裡多了個`CoverageVariableName`變數,其有三個比較關鍵的屬性: * `Count` uint32陣列,陣列中每個元素代表相應基本塊(basic block)被執行到的次數 * `Pos` 代表的各個基本塊在原始碼檔案中的位置,三個為一組。比如這裡的`21`代表該基本塊的起始行數,`23`代表結束行數,`0x2000d`比較有趣,其前16位代表結束列數,後16位代表起始列數。通過行和列能唯一確定一個點,而通過起始點和結束點,就能精確表達某基本塊在原始碼檔案中的物理範圍 * `NumStmt` 代表相應基本塊範圍內有多少語句(statement) `CoverageVariableName`變數會在每個執行邏輯單元設定個計數器,比如`CoverageVariableName.Count[0]++`, 而這就是所謂插樁了。通過這個計數器能很方便的計算出這塊程式碼是否被執行到,以及執行了多少次。相信大家一定見過表示go覆蓋率結果的coverprofile資料,類似下面: `github.com/qiniu/goc/goc.go:21.13,23.2 1 1` 這裡的內容就是通過類似上面的變數`CoverageVariableName`得到。其基本語義為 "**檔案:起始行.起始列,結束行.結束列 該基本塊中的語句數量 該基本塊被執行到的次數**" 依託於go語言官方強大的工具鏈,大家可以非常方便的做單測覆蓋率收集與統計。但是集測/E2E就不是那麼方便了。不過好在我們現在有了https://github.com/qiniu/goc。 ## 集測覆蓋率收集利器 - Goc原理 關於單測這塊,深入go原始碼,我們會發現`go test -cover`命令會自動生成一個`_testmain.go` 檔案。這個檔案會Import各個插過樁的包,這樣就可以直接讀取插樁變數,從而計算測試覆蓋率。實際上`goc`也是類似的原理(PS: 關於為何不直接用`go test -c -cover` 方案,可以參考這裡https://mp.weixin.qq.com/s/DzXEXwepaouSuD2dPVloOg)。 不過集測時,被測物件通常是完整產品,涉及到多個long running的後端服務。所以goc在設計上會自動化會給每個服務注入HTTP API,同時通過服務註冊中心`goc server`來管理所有被測服務。如此的話,就可以在執行時,通過命令`goc profile`實時獲取整個叢集的覆蓋率結果,當真非常方便。 整體架構參見: ![](https://img2020.cnblogs.com/blog/293394/202011/293394-20201107175410506-1726325935.png) ## 程式碼覆蓋率的最佳實踐 技術需要為企業價值服務,不然就是在耍流氓。可以看到,目前玩覆蓋率的,主要有以下幾個方向: * 度量 - 深度度量,各種包,檔案,方法度量,都屬於該體系。其背後的價值在於反饋與發現。反饋測試水平如何,發現不足或風險並予以提高。比如常見的作為流水線准入標準,釋出門禁等等。度量是基礎,但不能止步於資料。覆蓋率的終極目標,是提高測試覆蓋率,尤其是自動化場景的覆蓋率,並一以貫之。所以基於此,業界我們看到,做的比較有價值的落地形態是增量覆蓋率的度量。goc diff 結合Prow平臺也落地了類似的能力,如果您內部也使用Kubernetes,不妨嘗試一下。當然同類型的比較知名的商業化服務,也有CodeCov/Coveralls等,不過目前她們多數是侷限在單測領域。 * 精準測試方向 - 這是個很大的方向,其背後的價值邏輯比較清晰,就是建立業務到程式碼的雙向反饋,用於提升測試行為的精準高效。但這裡其實含有悖論,懂程式碼的同學,大概率不需要無腦反饋;不能深入到程式碼的同學,你給程式碼級別的反饋,也效果不大。所以這裡落地姿勢很重要。目前業界沒還看到有比較好的實踐例子,大部分都是解決特定場景下的問題,有一定的侷限。 而相較於落地方向,作為廣大研發同學,下面這些最佳實踐可能對您更有價值: * 高程式碼覆蓋率並不能保證高產品質量,但低程式碼覆蓋率一定說明大部分邏輯沒有被自動化測到。後者通常會增加問題遺留到線上的風險,當引起注意。 * 沒有普適的針對所有產品的嚴格覆蓋率標準。實際上這更應該是業務或技術負責人基於自己的領域知識,程式碼模組的重要程度,修改頻率等等因素,自行在團隊中確定標準,並推動成為團隊共識。 * 低程式碼覆蓋率並不可怕,能夠主動去分析未被覆蓋到的部分,並評估風險是否可接受,會更加有意義。實際上筆者認為,只要這一次的提交比上一次要好,都是值得鼓勵的。 谷歌有篇部落格(參考資料)提到,其經驗表明,重視程式碼覆蓋率的團隊通常會更加容易培養卓越工程師文化,因為這些團隊在設計產品之初就會考慮可測性問題,以便能更輕鬆的實現測試目標。而這些措施反過來會促使工程師編寫更高質量的程式碼,更注重模組化。 最後,歡迎點選左下角詳情按鈕,加入七牛雲Goc交流群,我們一起聊聊goc,聊聊研發效能那些事。 參考資料 * 谷歌管理覆蓋率的經驗: https://testing.googleblog.com/2020/08/code-coverage-best-practices.html ### 往期推薦 * [開源啦 | go語言系統測試覆蓋率收集利器goc](https://mp.weixin.qq.com/s/NUApZZnQALkzvZtHFE_8Zw) #### 覺得不錯,歡迎關注: ![](https://img2018.cnblogs.com/blog/293394/202001/293394-20200129180656049-6369771