Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 亚洲婷婷国产精品电影人久久,mmmmxxxx国产在线观看,99精品在线视频观看

          整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          如何使用Golang的Gin框架渲染HTML頁面

          如何使用Golang的Gin框架渲染HTML頁面

          用Golang流行的web框架Gin渲染HTML模板頁面的簡單例子

          Gin是Golang最流行的web框架之一。我之前已經寫過如何使用Golang基礎模板包渲染HTML頁面。使用Gin渲染HTML模板更加簡單。

          為了使工作流更加順暢,嘗試新的想法并進行調試,我還決定使用Codegangsta的自動重載工具Gin。

          安裝Gin

          安裝Gin HTTP web框架就像安裝大多數(如果不是所有)Golang包一樣簡單:

          go get -u github.com/gin-gonic/gin

          Gin 代碼

          package main
          
          import (
              "html/template"
              "net/http"
              "strings"
          
              "github.com/gin-gonic/gin"
          )
          
          func main() {
              router :=gin.Default()
              router.SetFuncMap(template.FuncMap{
                  "upper": strings.ToUpper,
              })
              router.Static("/assets", "./assets")
              router.LoadHTMLGlob("templates/*.html")
          
              router.GET("/", func(c *gin.Context) {
                  c.HTML(http.StatusOK, "index.html", gin.H{
                      "content": "This is an index page...",
                  })
              })
          
              router.GET("/about", func(c *gin.Context) {
                  c.HTML(http.StatusOK, "about.html", gin.H{
                      "content": "This is an about page...",
                  })
              })
              router.Run("localhost:8080")
          }

          引入的包

          在第4到7行,我們引入了一些包:

          • ? Gin HTTP web框架的包。
          • ? Golang的html/template基礎包,用于引入FuncMap()函數。在模板中使用函數時需要此函數。
          • ? 與Gin一起使用的Golang net/http基礎包。
          • ? strings基礎包,用于FuncMap中的upper函數。

          main()函數

          在第11行,我們創建了一個名為router的默認Gin路由。默認的Gin路由使用日志和恢復中間件,以及基本功能。

          在第12到14行,使用SetFuncMap()創建一個供模板使用的函數映射。這里我們添加了一個簡單的模板函數upper,它使用strings.ToUpper()函數將字符串中的所有字符設置為大寫。

          在第15行,我們讓Gin路由知道我們在./assets目錄中保存了一些靜態資產。Gin可以以這種方式訪問任何靜態資源。

          在這個例子中,我在那個目錄中放了一個Bulma CSS庫的最小版本。通過使用Static()函數,現在HTML模板可以訪問這個庫。

          在第16行,所有滿足template/*.html模式的模板都由LoadHTMLGlob()函數加載。這個模式意味著模板文件應該有.html的擴展名,并且位于/template目錄中。

          在第18到22行,我們告訴Gin路由接受URL路徑/上的HTTP GET方法請求。當收到請求時,Gin發送一個HTTP OK狀態消息,并用gin.H{}括號內提供的數據渲染index.html模板。在這種情況下,數據只包括一個鍵/值對,鍵名為content

          在第24到28行,與上面類似,我們告訴Gin路由接受/about路徑上的HTTP GET方法請求。這次渲染的是about.html模板。

          在第29行,我們讓Gin在localhost端口8080上運行web服務器。

          模板和目錄結構

          下面你可以找到本例中使用的四個模板。這些模板每個都需要在自己的文件中。每段代碼之前都有文件名。

          模板的語法與html/template基礎包中的相同。你可以我之前的文章中閱讀更多關于模板語法的內容。

          // header.html
          {{define "header.html"}}
          <!DOCTYPE html>
          <html>
          <head>
              <meta charset="utf-8">
              <meta name="viewport" content="width=device-width, initial-scale=1">
              <link rel="stylesheet" type="text/css" href="/assets/bulma.min.css">
              <title></title>
          </head>
          <body>
          <div class="container">
          <br><br>
          {{end}}
            
          
          // footer.html
          {{define "footer.html"}}
          </div>
          </body>
          </html>
          {{end}}
          
          
          // index.html
          {{template "header.html"}}
          <h1 class="title">
              INDEX PAGE
          </h1>
          <p>{{ .content }}</p>
          {{template "footer.html"}}
          
          
          // about.html
          {{template "header.html"}}
          <h1 class="title">
              ABOUT PAGE
          </h1>
          <p>{{ .content | upper}}</p>
          {{template "footer.html"}}

          自動重載Gin

          如前所述,我在開發時使用codegangsta/gin工具來自動重載gin。這使得在瀏覽器中檢查我們代碼的結果變得更加容易。當我們改變了代碼,無需停止當前的可執行文件,重新構建和運行它,就可以做到這一點。

          以下是Github頁面上對這個工具的描述:

          gin是一個簡單的命令行工具,用于實時重新加載Go web應用程序。只需在你的應用程序目錄下運行gin,你的web應用就會以gin作為代理來服務。gin會在檢測到更改時自動重新編譯你的代碼。下次它接收到HTTP請求時,你的應用將會重啟。

          安裝codegangsta/gin

          go get github.com/codegangsta/gin

          運行codegangsta/gin

          gin -i --appPort 8080 --port 3000 run main.go

          上述代碼需要在命令行中運行。它假設你正在localhost8080端口上運行你的Go web應用,并且你將使用3000端口作為Gin自動重載代理。

          關于運行代碼的注意事項

          在從命令行運行代碼之前,你可能需要運行以下命令:

          go mod init
          go mod tidy

          在網頁瀏覽器中檢查結果

          在瀏覽器的URL字段中輸入以下內容以檢查代碼的結果:

          localhost:3000/

          …對于索引頁面,和…

          localhost:3000/about

          對于關于頁面。

          列表清單

          請記住,始終保持學習的態度,并享受編碼的樂趣!祝您編碼愉快!

          如果你喜歡我的文章,點贊,關注,轉發!

          在移動端平臺開發中,為了增加代碼復用,降低開發成本,通常會需要采用跨平臺的開發技術,花椒也不例外。本次新的單品開發,由于時間緊,人員有限,經過調研選型,最終確定了 flutter 方案(具體選型過程不在本文討論之內)。

          為了讓客戶端更專注業務實現,降低接口聯調測試成本,我們選用了 gRPC 方案。gRPC是一個高性能、通用的開源 RPC 框架,由 Google 開發并基于 HTTP/2 協議標準而設計,基于 ProtoBuf(Protocol Buffers)序列化協議開發,且支持當前主流開發語言。gRPC通過定義一個服務并指定一個可以遠程調用的帶有參數和返回類型的的方法,使客戶端可以直接調用不同機器上的服務應用的方法,就像是本地對象一樣。在服務端,服務實現這個接口并且運行 gRPC 服務處理客戶端調用。在客戶端,有一個stub提供和服務端相同的方法。

          gRPC

          特點

          • 基于標準化的 IDL(ProtoBuf)來生成服務器端和客戶端代碼,支持多種主流開發語言。同時可以更好的支持團隊與團隊之間的接口設計,開發,測試,協作等。
          • 基于 HTTP/2 設計,支持雙向流,多路復用,頭部壓縮。
          • 支持流式發送和響應,批量傳輸數據,提升性能。
          • ProtoBuf 序列化數據抓包、調試難度較大。我們使用服務端注入方式提供了用戶或設備過濾,請求及返回值日志捕獲,并開發對應后臺模擬抓包展示。
          • 相比 JSON, 對前端不夠友好。gRPC 生態 提供了 gateway 的方式為 gRPC 服務代理出 RESTful 接口。
          • ProtoBuf 提供了非常強的擴展性,可以為 protoc 開發定制插件,從而擴展 proto 文件的功能及描述性。

          gRPC-Web

          gRPC-Web 為前端瀏覽器提供了 Javascript 庫用來訪問 gRPC 服務,但是需要通過 Envoy 提供代理服務。相比 JSON 的方式對前端不夠友好,同時也增加了服務端的部署成本。因此在這次項目中前端未使用 gRPC 服務,而是由 gRPC-Gateway 提供代理的 RESTful 接口。

          gRPC-Gateway

          grpc-gateway 是 protoc 的一個插件,它能讀取 gRPC 的服務定義并生成反向代理服務器,將 RESTful 的 JSON 請求轉換為 gRPC 的方式。這樣無需太多工作即可實現一套基于 gRPC 服務的 RESTful 接口,方便前端使用調用接口,同時也方便開發過程中通過 Postman/Paw 之類的工具調試接口。

          gateway -> gRPC 映射方式:

          • HTTP 源 IP 添加到 gRPC 的 X-Forwarded-For 請求頭
          • HTTP 請求 Host 添加到 gRPC 的 X-Forwarded-Host 請求頭
          • HTTP 請求頭 Authorization 添加到 gRPC 的 authorization 請求頭
          • HTTP 請求頭帶 Grpc-Metadata- 前綴的映射到 gRPC 的 metadata (key 名不帶前綴)

          例如,gRPC 接口要求的通用的 metadata 參數(如 platform, device_id 等)在 HTTP RESTful 的傳遞方式如下:

          基礎庫

          dart

          為了便于客戶端調用,連接復用及通用參數傳遞,我們封裝了 dart 的基礎庫。

          BaseClient 維護了針對 HOST 緩存的連接池,同時也提供了接口需要傳遞的 metadata 信息。

          golang

          golang 后端服務需要同時支持 gRPC 和 gateway 兩種請求方式。為了簡化部署和上線依賴,gateway 和 gRPC 的功能放在了一起,并通過攔截器注入對應的功能,主要包括 gRPC 統計,訪問日志,接口鑒權,請求參數校驗,gateway JSON 編碼等。

          • 引用到的 package


          • 開發流程

          為了提高開發效率,方便維護及模塊復用,服務端按功能進行組件化開發。每個組件可以單獨運行一個服務,也可以和其它組件共同組成一個服務。每個組件都需要實現 Component 接口:

          對應組件開發完成后,需要開發對應的服務容器,步驟如下。

          • 初始化 base package
          1 base.Init(context.TODO(), cfg, &global.Callback{
          2 Authenticator: &auth.Callback{},
          3 LogCapture: &log.Capture{},
          4 })
          
          • 如需對外提供服務,需要提供端口及 TLS 證書


          base.DefaultServer.AddPublicServer(rpcPort, gatewayPort, setting.TLSConfig)

          • 組件注冊
          1 base.DefaultServer.RegisterComponent(&user.Component{})
          2 base.DefaultServer.RegisterComponent(&push.Component{})
          3 ...
          
          • 監聽服務


          base.DefaultServer.Serve()

          接口定義及實現

          proto 規范

          gRPC 基于標準化的 IDL(ProtoBuf)來生成服務器端和客戶端代碼,我們決定將所有的接口描述及文檔說明都放到 proto 文件中,便于查看及修改。對 proto 的接口描述及注釋的規范如下:

          代碼生成

          golang

           1 gengo:
           2 @protoc -Iproto \
           3 -I${GOPATH}/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
           4 -I${GOPATH}/src/github.com/lnnujxxy/protoc-gen-validate \
           5 -I${GOPATH}/src/github.com/youlu-cn/grpc-gen/protoc-gen-auth \
           6 --go_out=plugins=grpc:go/pb \
           7 --grpc-gateway_out=logtostderr=true:go/pb \
           8 --validate_out="lang=go:go/pb" \
           9 --auth_out="lang=go:go/pb" \
          10 proto/*.proto
          
          • SDK 引入


          golang 使用 go mod 的方式直接引入 pb 生成的 .go 文件

          dart

          • SDK 引入


          修改 pubspec.yaml,執行 flutter packages get 或 flutter packages upgrade

           1 dependencies:
           2 flutter:
           3 sdk: flutter
           4
           5 protobuf: ^0.13.4
           6 grpc: ^1.0.1
           7 user:
           8 git:
           9 url: git@github.com:project/repo.git
          10 path: dart/user
          
          • 已知問題:
          1. dart 在對 protobuf 生成的類型做 json 編碼時,json 中的 key 是字段號而非名字,導致無法與其它語言交互。ISSUE (https://github.com/dart-lang/protobuf/issues/220)


          文檔生成

          gRPC gateway 提供了通過 proto 文件生成 swagger API 文檔,缺點是只支持 gateway 的 RESTful 接口,并且默認的展示方式有點不符合我們的常規文檔使用方式。

          我們基于 protoc 插件開發了 protoc-gen-markdown 工具,可以由 proto 文件生成 markdown 文檔,提供 gRPC 接口描述,以及 RESTful 接口描述及 JSON 示例,提供全文目錄,支持錨點導航等。生成方式如下:

          1gendoc:
          2 @protoc -Iproto \
          3 -I${GOPATH}/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
          4 -I${GOPATH}/src/github.com/lnnujxxy/protoc-gen-validate \
          5 -I${GOPATH}/src/github.com/youlu-cn/grpc-gen/protoc-gen-auth \
          6 --markdown_out=":doc" \
          7 proto/*.proto
          

          文檔會在對應路徑生成接口列表 README.md,以及每個 protobuf 對應的接口文檔。

          調試

          傳統的 RESTful 接口在調試及問題排查時,可以通過抓包或者 MitM(中間人攻擊)的方式,配置也比較容易。而 gRPC 因為使用了 HTTP2 及 protobuf 二進制流,抓包及數據流反解難度相對較高,調試及問題排查時會比較復雜。為了解決這個問題,我們通過服務端注入的方式,配合查詢后臺過濾對應的請求日志,從而實現如下類似抓包的效果。



          后續計劃

          1. gRPC Streaming
          2. 框架層集成全鏈路 Trace 支持
          3. 迭代優化框架,提供對應腳手架,簡化新組件/服務創建及開發流程

          Go語言中文網,致力于每日分享編碼、開源等知識,歡迎關注我,會有意想不到的收獲!

          本文由花椒服務端團隊原創授權發布

          近在給項目代碼完善單元測試,發現go語言單元測試相關的資料都是零零散散的,所以在這兒整理總結一下。項目中使用的是goconvey+monkey+sqlmock (項目的web框架為gin, 持久層框架為gorm), 使用時也碰到一些坑,也會在這篇文章中做一些相關的記錄。 文章大約4200字,囿于篇幅,很多地方都是一筆帶過,不過在每一部分之后提供了一些筆者讀過覺得不錯的資料的鏈接,大家可以根據需要查看。

          1. Go對單元測試的原生支持

          1.1 testing——Go內置的單元測試庫。

          要編寫一個新的測試,需要創建一個以 _test.go 結尾的文件,該文件包含 TestXxx 函數。 將該文件放在與被測試的包相同的包中。

          通過 go test 命令,能夠自動執行如下形式的任何函數:

          func TestXxx(*testing.T)
          

          注意:Xxx 可以是任何字母數字字符串,但是第一個字母不能是小寫字母(一般接被測試函數名字,不強求)。傳遞給測試函數的參數是 *testing.T 類型。它用于管理測試狀態并支持格式化測試日志。測試日志會在執行測試的過程中不斷累積,并在測試完成時轉儲至標準輸出。

          詳情參見:The-Golang-Standard-Library-by-Example

          https://books.studygolang.com/The-Golang-Standard-Library-by-Example/chapter09/09.1.html

          1.2 TestMain

          在寫測試時,有時需要在測試之前或之后進行額外的設置(setup)或拆卸(teardown);有時,測試還需要控制在主線程上運行的代碼。為了支持這些需求,testing 提供了 TestMain 函數:

          func TestMain(m *testing.M)
          

          如果測試文件中包含該函數,那么生成的測試將調用 TestMain(m),而不是直接運行測試。

          TestMain 運行在主 goroutine 中, 可以在調用 m.Run 前后做任何設置和拆卸。注意,在 TestMain 函數的最后,應該使用 m.Run 的返回值作為參數調用 os.Exit。

          詳情參見:TestMain

          https://books.studygolang.com/The-Golang-Standard-Library-by-Example/chapter09/09.5.html#testmain

          1.3 httptest——HTTP測試輔助工具

          Go 標準庫專門提供了 httptest 包專門用于進行 http Web 開發測試。

          httptest包最關鍵的是提供了一個 http.ReponseWriter接口的實現結構:httptest.ReponseRecorder,通過它可以得到一個http.ReponseWriter,并以此來接收服務器返回的響應包。

          詳情參見:httptest - HTTP 測試輔助工具

          https://books.studygolang.com/The-Golang-Standard-Library-by-Example/chapter09/09.6.html

          1.4 測試覆蓋率

          Go 從 1.2 開始,引入了測試覆蓋率的支持,使用的是 cover 相關的工具(go test -cover、go tool cover)。

          詳情參見:The cover story

          https://blog.golang.org/cover

          2. 斷言庫

          Go標準包里并沒有斷言庫,但是不使用斷言庫進行結果校驗的話,測試代碼將會變得非常臃腫,可讀性和可維護性都會很差。不過好在有第三方框架可以讓我們使用。

          2.1 testify

          github地址:https://github.com/stretchr/testify

          特性:

          • 在提供斷言功能之外,還提供了mock的功能
          • suite包可以給每個測試用例進行前置操作和后置操作的功能(例如初始化和清空數據庫)

          2.2 gocheck

          godoc地址:https://godoc.org/gopkg.in/check.v1

          特性:

          • 豐富了單元測試常用的 assert 斷言,判斷動詞deep multi-type 對比,字符串比較以及正則匹配
          • 測試用例組織集合方面按suite組織測試用例,支持suite級別的 setup() 和 teardown()
          • 對于臨時文件支持創建、刪除臨時文件和目錄。

          詳情參見:gocheck 使用介紹

          https://zhuanlan.zhihu.com/p/45570168

          2.3 goconvey

          github地址: https://github.com/smartystreets/goconvey

          特性:

          • 直接集成go test
          • 可以管理和運行測試用例
          • 提供了豐富的斷言函數
          • 支持很多 Web 界面特性(通過http://localhost:8080訪問)
          • 設置界面主題
          • 查看完整的測試結果
          • 使用瀏覽器提醒
          • 自動檢測代碼變動并編譯測試
          • 半自動化書寫測試用例:http://localhost:8080/composer.html
          • 查看測試覆蓋率:http://localhost:8080/reports/
          • 臨時屏蔽某個包的編譯測試

          詳情參見:GoConvey框架使用指南

          https://www.jianshu.com/p/e3b2b1194830

          2.4 比較

          其實gocheck我沒怎么用過,只是當時調研的時候看到了,在斷言方面看起來和其他的差不多。

          testify和goconvey都有嘗試,最后采用的是goconvey,所以對goconvey更熟悉一點。

          • testify
          • star數和活躍度較高:這個其實挺重要的,因為很多人都在用這個框架的話,這個框架會得到更好的完善和發展,也會更有生命力。在使用時碰到的問題也可以很方便的在issue中找到答案;
          • testify類似于gocheck和gomock的結合體,但是其mock使用并不是很方便,所以建議還是使用專門的mock框架。
          • goconvey
          • 可以管理和運行測試用例,通過嵌套來體現測試用例之間的關系。這是我當時選擇使用goconvey的一個很重要原因,它可以將測試代碼組織得更富邏輯性和結構化,提高了測試代碼的可讀性和可維護性
          • 支持在web界面進行自動化編譯測試。之前在油管上看到一個博主通過web界面來半自動化生成測試代碼。不過我實際并沒有使用這個功能,感覺這個功能適合邏輯簡單/清楚的代碼。

          3 mock/stub方案

          3.1 識別依賴

          普遍來說,我們遇到最常見的依賴無非下面幾種:

          • 網絡依賴——函數執行依賴于網絡請求,比如第三方http-api,rpc服務,消息隊列等
          • 數據庫依賴
          • I/O依賴(文件)
          • 還未開發完成的功能模塊

          3.2 mock和stub的區別

          這個話題也算是老生常談了。幾句話很難解釋清楚,有興趣可以閱讀Martin Fowler的文章。

          stub本質上是對真實對象的一個模擬,比如調用者需要一個值,那就讓stub輸出一個值,如果調用者需要傳遞一個值給stub,那就在stub中定義一個方法接受該參數,相當于“依賴部分”的一個簡化實現。mock則是在程序代碼中向被測試代碼注入“依賴部分”,模擬出函數調用返回的結果。

          個人認為兩者最大的區別在于依賴對象是否和被測對象有交互,從結果來看,stub不會使測試失敗,它只是為被測對象提供依賴的對象,并不改變測試結果,而mock則會根據不同的交互測試要求,很可能會更改測試的結果。stub是state-based,關注的是輸入和輸出。mock是interaction-based,關注的是交互過程。

          mock和stub還有一個重要的區別就是expectiation。對于mock來說,expectiation是重中之重:我們期待方法有沒有被調用,期待適當的參數,期待調用的次數,甚至期待多個mock之間的調用順序。所有的一切期待都是事先準備好,在測試過程中和測試結束后驗證是否和預期的一致。而對于stub,通常都不會關注expectiation,沒有任何代碼來幫助判斷這個stub類是否被調用。雖然理論上某些stub實現也可以通過自己編碼的方式增加對expectiation的內容,比如增加一個計數器,每次調用+1之類,但是實際上極少這樣做。

          在Go中,如果要用stub,那將是侵入式的,必須將代碼設計成可以用stub方法替換的形式。為了測試,需要專門用一個全局變量 來保存具有外部依賴的方法。然而在不提倡使用全局變量的Go語言當中,這顯然是不合適的。所以,并不提倡這種Stub方式。

          但其實這兩種方法并不是割裂的,例如像下文提到的gomock框架除了像其名字一樣可以mock對象以外,還提供了stub的功能。軟件工程沒有銀彈,我們需要根據合適的場景選用合適的方法,甚至可以結合多種方法使用。

          詳情參見:Mocks Aren't Stubs(Martin Fowler)

          https://martinfowler.com/articles/mocksArentStubs.html

          以及 中文翻譯

          https://www.cnblogs.com/anf/archive/2006/03/27/360248.html

          3.3 gostub

          github地址:https://github.com/prashantv/gostub

          特性:

          • 可以為全局變量、函數、過程打樁
          • 比gomock輕量,不需要依賴接口

          缺陷:

          • 對項目源代碼有侵入性,即被打樁方法必須賦值給一個變量,只有以這種形式定義的方法才能別打樁

          詳情參見:GoStub框架使用指南

          3.4 gomock

          github地址:https://github.com/golang/mock

          特性:

          • golang官方開發維護的接口級別的mock方案
          • 包含了GoMock包和mockgen工具兩部分,其中GoMock包完成對樁對象生命周期的管理,mockgen工具用來生成interface對應的Mock類源文件。

          缺陷:

          • 只有以接口定義的方法才能mock
          • 需要用mockgen生成源文件,然后用gomock去實現自己想要的數據,用法稍重。

          詳情參見:使用Golang的官方mock工具—gomock

          https://www.jianshu.com/p/598a11bbdafb

          和 GoMock框架使用指南

          https://www.jianshu.com/p/f4e773a1b11f

          3.5 gomonkey

          github地址:https://github.com/bouk/monkey

          特性:

          • 可以為全局變量、函數、過程、方法打樁,同時避免了gostub對代碼的侵入

          缺陷:

          • 對inline函數打樁無效
          • 不支持多次調用樁函數(方法)而呈現不同行為的復雜情況

          詳情參見:Monkey框架使用指南

          https://www.jianshu.com/p/2f675d5e334e

          3.6 sqlmock

          github地址: https://github.com/DATA-DOG/go-sqlmock

          特性:

          • 適用于和數據庫的交互場景。可以創建模擬連接,編寫原生sql 語句,編寫返回值或者錯誤信息并判斷執行結果和預設的返回值
          • 提供了完整的事務的執行測試框架,支持prepare參數化提交和執行的Mock方案
          • 持久層框架底層一般都使用”github.com/go-sql-driver/mysql”,所以一般都能夠使用sqlmock庫進行mock

          缺陷:

          • 因為是正則匹配,所以可能漏掉sql的語法錯誤
          • 寫入后沒法驗證

          3.7 httpexpect

          github地址:https://github.com/gavv/httpexpect

          特性:

          • 適用于對http的clent進行測試,對服務端的回包進行打樁
          • 支持對不同方法(get,post,head等)的構造,支持自定義返回值json

          sqlmock和httpexpect都蠻簡單的,看完github主頁的QuickStart基本就會用了~~

          4 使用goconvey+gomonkey+sqlmock進行測試

          4.1 選擇原因

          • 外層框架——goconvey。項目代碼很多邏輯比較復雜,需要編寫不同情況下的測試用例,用goconvey組織的測試代碼邏輯層次比較清晰,有著較好的可讀性和可維護性。斷言方面感覺convey和testify功能差不多。不過convey沒有testify社區活躍度高,后續使用convey時碰到一些問題,都不太容易找到解決辦法,給作者提issue,感覺回復效率也不是很高。
          • 函數mock——gomonkey。項目代碼基本都不是基于interface實現的,所以不太方便使用gomock,項目目前運行穩定,所以也不想因為單元測試重構原來的代碼,所以也不太方便gostub。好在還有gomonkey可以用,基本符合我們對函數打樁的需求。
          • 持久層mock——sqlmock。我們持久層的框架是gorm。當時考慮2種方法進行mock,一種是使用gomonkey對gorm的函數進行mock,另一種則是選用sqlmock。但碰到下圖所示的sql語句,如果使用gomonkey的話需要對連續調用的gorm函數都進行mock,過于繁雜。而用sqlmock的話只需匹配對應的sql語句即可。
          newDB=MysqlDB.ModelTable(c, &Basexxx{}, c.AppID()).Where("type=?", libType).Limit(limit).Offset(offset).Order("created_at desc").Find(&libxxxs)
          

          4.2 gorm+sqlmock使用方法

          初始化sqlmock后,然后使用dialect和dsn打開一個新的gorm連接并賦值給數據庫操作實例

           _, mock, _=sqlmock.NewWithDSN("sqlmock_db")
           MysqlDB.DB, _=gorm.Open("sqlmock", "sqlmock_db")
          

          接下來就和sqlmock的普通使用沒什么區別了,只要mock時能夠成功的匹配gorm生成的sql語句即可

          詳情參見:Stub database connection with GORM

          https://blog.valletta.io/blog/2018-07-05-stub-database-connection-with-gorm/

          4.3 踩坑記錄(持續更新~)

          1. 問題描述:
          2. 測試函數在run的時候fail,在無斷點debug的時候pass。被patch的函數是下例中的函數A。
          func A(arg string) error {
           return B(arg)
          }
          

          原因:

          • run的時候會做編譯器優化,調用A會直接被優化為調用B(內聯)。所以對前者的patch并沒有成功。

          在不改動原有代碼的情況下,有2種解決方案:

          • 給函數B也打補丁
          • 在go test時加參數來避免編譯器優化內聯 go test -gcflags=-l

          5 其他

          5.1 單元測試的粒度

          對于剛開始做單元測試的同學來說,如何把握單元測試的粒度是一個讓人頭疼的問題。

          測試粒度做的太細,會耗費大量的開發以及維護時間,每改一個方法,都要改動其對應的測試方法。當發生代碼重構的時候那簡直就是噩夢(因為所有的單元測試又都要寫一遍了…)。

          如果單元測試粒度太粗,一個測試方法測試了n多方法,那么單元測試將顯的非常臃腫,脫離了單元測試的本意,容易把單元測試寫成集成測試。

          5.2 單元測試的成本和收益

          在受益于單元測試的好處的同時,也必然增加了代碼量以及維護成本。

          下面這張成本/價值象限圖清晰闡述了在不同性質的系統中單元測試的成本和價值之間的關系。

          1. 依賴很少的簡單代碼(左下)
          2. 對于外部依賴少,代碼又簡單的代碼。自然其成本和價值都是比較低的。
          3. 例如Go官方庫里errors包,整個包就兩個方法 New()和 Error(),沒有任何外部依賴,代碼也很簡單,所以其單元測試起來也是相當方便。
          4. 依賴較多的簡單代碼(右下)
          5. 依賴一多,mock和stub就必然增多,單元測試的成本也就隨之增加。但代碼又如此簡單,這個時候寫單元測試的成本已經大于其價值,還不如不寫單元測試。
          6. 依賴很少的復雜代碼 (左上)
          7. 像這一類代碼,是最有價值寫單元測試的。比如一些獨立的復雜算法(銀行利息計算,保險費率計算,TCP協議解析等),像這一類代碼外部依賴很少,但卻很容易出錯,如果沒有單元測試,幾乎不能保證代碼質量。
          8. 依賴很多的復雜代碼(右上)
          9. 這種代碼顯然是單元測試的噩夢。寫單元測試吧,代價高昂;不寫單元測試吧,風險太高。
          10. 像這種代碼我們盡量在設計上將其分為兩部分:1.處理復雜的邏輯部分 2.處理依賴部分 然后1部分進行單元測試。

          參考鏈接

          下面是一些其他的參考資料:

          1. The-Golang-Standard-Library-by-Example

          https://books.studygolang.com/The-Golang-Standard-Library-by-Example/

          2. 搞定Go單元測試(一)——基礎原理

          https://juejin.im/post/5ce93447e51d45775746b8b0#heading-12


          主站蜘蛛池模板: 免费一区二区无码视频在线播放| 一区二区视频在线| 精品一区二区三区波多野结衣| 国产乱人伦精品一区二区| 视频一区二区三区免费观看| AA区一区二区三无码精片| 国产精品熟女一区二区| 精品无码综合一区| 亚洲AV无码一区二区三区在线观看 | 国产在线精品观看一区| 日韩精品一区二区三区老鸭窝| 国精品无码一区二区三区在线蜜臀| 国产在线不卡一区| 成人欧美一区二区三区在线视频| 日韩一区二区超清视频| 麻豆国产一区二区在线观看| 亚洲一区影音先锋色资源| 无码日韩精品一区二区免费暖暖 | 久久精品中文字幕一区| 国产SUV精品一区二区88L| 视频精品一区二区三区| 国产成人一区二区动漫精品| 日韩精品一区二区三区在线观看l| 国产一区二区在线观看| 国产传媒一区二区三区呀| 视频一区在线免费观看| 日本一区午夜爱爱| 久久精品一区二区三区中文字幕| 综合久久一区二区三区| 亚洲电影一区二区| 国产一区二区成人| 波多野结衣一区二区三区高清av | 国产一区二区三区免费看 | 影院成人区精品一区二区婷婷丽春院影视 | 国产亚洲一区区二区在线| 国产激情无码一区二区| 久久精品一区二区三区AV| 日韩免费无码一区二区视频| 亚洲成a人一区二区三区| 一区二区三区国产| 亚洲综合av一区二区三区不卡|