整合營(yíng)銷(xiāo)服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢(xún)熱線(xiàn):

          如何使用Golang的Gin框架渲染HTML頁(yè)面

          如何使用Golang的Gin框架渲染HTML頁(yè)面

          用Golang流行的web框架Gin渲染HTML模板頁(yè)面的簡(jiǎn)單例子

          Gin是Golang最流行的web框架之一。我之前已經(jīng)寫(xiě)過(guò)如何使用Golang基礎(chǔ)模板包渲染HTML頁(yè)面。使用Gin渲染HTML模板更加簡(jiǎn)單。

          為了使工作流更加順暢,嘗試新的想法并進(jìn)行調(diào)試,我還決定使用Codegangsta的自動(dòng)重載工具Gin。

          安裝Gin

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

          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基礎(chǔ)包,用于引入FuncMap()函數(shù)。在模板中使用函數(shù)時(shí)需要此函數(shù)。
          • ? 與Gin一起使用的Golang net/http基礎(chǔ)包。
          • ? strings基礎(chǔ)包,用于FuncMap中的upper函數(shù)。

          main()函數(shù)

          在第11行,我們創(chuàng)建了一個(gè)名為router的默認(rèn)Gin路由。默認(rèn)的Gin路由使用日志和恢復(fù)中間件,以及基本功能。

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

          在第15行,我們讓Gin路由知道我們?cè)?/span>./assets目錄中保存了一些靜態(tài)資產(chǎn)。Gin可以以這種方式訪問(wèn)任何靜態(tài)資源。

          在這個(gè)例子中,我在那個(gè)目錄中放了一個(gè)Bulma CSS庫(kù)的最小版本。通過(guò)使用Static()函數(shù),現(xiàn)在HTML模板可以訪問(wèn)這個(gè)庫(kù)。

          在第16行,所有滿(mǎn)足template/*.html模式的模板都由LoadHTMLGlob()函數(shù)加載。這個(gè)模式意味著模板文件應(yīng)該有.html的擴(kuò)展名,并且位于/template目錄中。

          在第18到22行,我們告訴Gin路由接受URL路徑/上的HTTP GET方法請(qǐng)求。當(dāng)收到請(qǐng)求時(shí),Gin發(fā)送一個(gè)HTTP OK狀態(tài)消息,并用gin.H{}括號(hào)內(nèi)提供的數(shù)據(jù)渲染index.html模板。在這種情況下,數(shù)據(jù)只包括一個(gè)鍵/值對(duì),鍵名為content

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

          在第29行,我們讓Gin在localhost端口8080上運(yùn)行web服務(wù)器。

          模板和目錄結(jié)構(gòu)

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

          模板的語(yǔ)法與html/template基礎(chǔ)包中的相同。你可以我之前的文章中閱讀更多關(guān)于模板語(yǔ)法的內(nèi)容。

          // 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"}}

          自動(dòng)重載Gin

          如前所述,我在開(kāi)發(fā)時(shí)使用codegangsta/gin工具來(lái)自動(dòng)重載gin。這使得在瀏覽器中檢查我們代碼的結(jié)果變得更加容易。當(dāng)我們改變了代碼,無(wú)需停止當(dāng)前的可執(zhí)行文件,重新構(gòu)建和運(yùn)行它,就可以做到這一點(diǎn)。

          以下是Github頁(yè)面上對(duì)這個(gè)工具的描述:

          gin是一個(gè)簡(jiǎn)單的命令行工具,用于實(shí)時(shí)重新加載Go web應(yīng)用程序。只需在你的應(yīng)用程序目錄下運(yùn)行gin,你的web應(yīng)用就會(huì)以gin作為代理來(lái)服務(wù)。gin會(huì)在檢測(cè)到更改時(shí)自動(dòng)重新編譯你的代碼。下次它接收到HTTP請(qǐng)求時(shí),你的應(yīng)用將會(huì)重啟。

          安裝codegangsta/gin

          go get github.com/codegangsta/gin

          運(yùn)行codegangsta/gin

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

          上述代碼需要在命令行中運(yùn)行。它假設(shè)你正在localhost8080端口上運(yùn)行你的Go web應(yīng)用,并且你將使用3000端口作為Gin自動(dòng)重載代理。

          關(guān)于運(yùn)行代碼的注意事項(xiàng)

          在從命令行運(yùn)行代碼之前,你可能需要運(yùn)行以下命令:

          go mod init
          go mod tidy

          在網(wǎng)頁(yè)瀏覽器中檢查結(jié)果

          在瀏覽器的URL字段中輸入以下內(nèi)容以檢查代碼的結(jié)果:

          localhost:3000/

          …對(duì)于索引頁(yè)面,和…

          localhost:3000/about

          對(duì)于關(guān)于頁(yè)面。

          列表清單

          請(qǐng)記住,始終保持學(xué)習(xí)的態(tài)度,并享受編碼的樂(lè)趣!祝您編碼愉快!

          如果你喜歡我的文章,點(diǎn)贊,關(guān)注,轉(zhuǎn)發(fā)!

          在移動(dòng)端平臺(tái)開(kāi)發(fā)中,為了增加代碼復(fù)用,降低開(kāi)發(fā)成本,通常會(huì)需要采用跨平臺(tái)的開(kāi)發(fā)技術(shù),花椒也不例外。本次新的單品開(kāi)發(fā),由于時(shí)間緊,人員有限,經(jīng)過(guò)調(diào)研選型,最終確定了 flutter 方案(具體選型過(guò)程不在本文討論之內(nèi))。

          為了讓客戶(hù)端更專(zhuān)注業(yè)務(wù)實(shí)現(xiàn),降低接口聯(lián)調(diào)測(cè)試成本,我們選用了 gRPC 方案。gRPC是一個(gè)高性能、通用的開(kāi)源 RPC 框架,由 Google 開(kāi)發(fā)并基于 HTTP/2 協(xié)議標(biāo)準(zhǔn)而設(shè)計(jì),基于 ProtoBuf(Protocol Buffers)序列化協(xié)議開(kāi)發(fā),且支持當(dāng)前主流開(kāi)發(fā)語(yǔ)言。gRPC通過(guò)定義一個(gè)服務(wù)并指定一個(gè)可以遠(yuǎn)程調(diào)用的帶有參數(shù)和返回類(lèi)型的的方法,使客戶(hù)端可以直接調(diào)用不同機(jī)器上的服務(wù)應(yīng)用的方法,就像是本地對(duì)象一樣。在服務(wù)端,服務(wù)實(shí)現(xiàn)這個(gè)接口并且運(yùn)行 gRPC 服務(wù)處理客戶(hù)端調(diào)用。在客戶(hù)端,有一個(gè)stub提供和服務(wù)端相同的方法。

          gRPC

          特點(diǎn)

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

          gRPC-Web

          gRPC-Web 為前端瀏覽器提供了 Javascript 庫(kù)用來(lái)訪問(wèn) gRPC 服務(wù),但是需要通過(guò) Envoy 提供代理服務(wù)。相比 JSON 的方式對(duì)前端不夠友好,同時(shí)也增加了服務(wù)端的部署成本。因此在這次項(xiàng)目中前端未使用 gRPC 服務(wù),而是由 gRPC-Gateway 提供代理的 RESTful 接口。

          gRPC-Gateway

          grpc-gateway 是 protoc 的一個(gè)插件,它能讀取 gRPC 的服務(wù)定義并生成反向代理服務(wù)器,將 RESTful 的 JSON 請(qǐng)求轉(zhuǎn)換為 gRPC 的方式。這樣無(wú)需太多工作即可實(shí)現(xiàn)一套基于 gRPC 服務(wù)的 RESTful 接口,方便前端使用調(diào)用接口,同時(shí)也方便開(kāi)發(fā)過(guò)程中通過(guò) Postman/Paw 之類(lèi)的工具調(diào)試接口。

          gateway -> gRPC 映射方式:

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

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

          基礎(chǔ)庫(kù)

          dart

          為了便于客戶(hù)端調(diào)用,連接復(fù)用及通用參數(shù)傳遞,我們封裝了 dart 的基礎(chǔ)庫(kù)。

          BaseClient 維護(hù)了針對(duì) HOST 緩存的連接池,同時(shí)也提供了接口需要傳遞的 metadata 信息。

          golang

          golang 后端服務(wù)需要同時(shí)支持 gRPC 和 gateway 兩種請(qǐng)求方式。為了簡(jiǎn)化部署和上線(xiàn)依賴(lài),gateway 和 gRPC 的功能放在了一起,并通過(guò)攔截器注入對(duì)應(yīng)的功能,主要包括 gRPC 統(tǒng)計(jì),訪問(wèn)日志,接口鑒權(quán),請(qǐng)求參數(shù)校驗(yàn),gateway JSON 編碼等。

          • 引用到的 package


          • 開(kāi)發(fā)流程

          為了提高開(kāi)發(fā)效率,方便維護(hù)及模塊復(fù)用,服務(wù)端按功能進(jìn)行組件化開(kāi)發(fā)。每個(gè)組件可以單獨(dú)運(yùn)行一個(gè)服務(wù),也可以和其它組件共同組成一個(gè)服務(wù)。每個(gè)組件都需要實(shí)現(xiàn) Component 接口:

          對(duì)應(yīng)組件開(kāi)發(fā)完成后,需要開(kāi)發(fā)對(duì)應(yīng)的服務(wù)容器,步驟如下。

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


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

          • 組件注冊(cè)
          1 base.DefaultServer.RegisterComponent(&user.Component{})
          2 base.DefaultServer.RegisterComponent(&push.Component{})
          3 ...
          
          • 監(jiān)聽(tīng)服務(wù)


          base.DefaultServer.Serve()

          接口定義及實(shí)現(xiàn)

          proto 規(guī)范

          gRPC 基于標(biāo)準(zhǔn)化的 IDL(ProtoBuf)來(lái)生成服務(wù)器端和客戶(hù)端代碼,我們決定將所有的接口描述及文檔說(shuō)明都放到 proto 文件中,便于查看及修改。對(duì) proto 的接口描述及注釋的規(guī)范如下:

          代碼生成

          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,執(zhí)行 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
          
          • 已知問(wèn)題:
          1. dart 在對(duì) protobuf 生成的類(lèi)型做 json 編碼時(shí),json 中的 key 是字段號(hào)而非名字,導(dǎo)致無(wú)法與其它語(yǔ)言交互。ISSUE (https://github.com/dart-lang/protobuf/issues/220)


          文檔生成

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

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

          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
          

          文檔會(huì)在對(duì)應(yīng)路徑生成接口列表 README.md,以及每個(gè) protobuf 對(duì)應(yīng)的接口文檔。

          調(diào)試

          傳統(tǒng)的 RESTful 接口在調(diào)試及問(wèn)題排查時(shí),可以通過(guò)抓包或者 MitM(中間人攻擊)的方式,配置也比較容易。而 gRPC 因?yàn)槭褂昧?HTTP2 及 protobuf 二進(jìn)制流,抓包及數(shù)據(jù)流反解難度相對(duì)較高,調(diào)試及問(wèn)題排查時(shí)會(huì)比較復(fù)雜。為了解決這個(gè)問(wèn)題,我們通過(guò)服務(wù)端注入的方式,配合查詢(xún)后臺(tái)過(guò)濾對(duì)應(yīng)的請(qǐng)求日志,從而實(shí)現(xiàn)如下類(lèi)似抓包的效果。



          后續(xù)計(jì)劃

          1. gRPC Streaming
          2. 框架層集成全鏈路 Trace 支持
          3. 迭代優(yōu)化框架,提供對(duì)應(yīng)腳手架,簡(jiǎn)化新組件/服務(wù)創(chuàng)建及開(kāi)發(fā)流程

          Go語(yǔ)言中文網(wǎng),致力于每日分享編碼、開(kāi)源等知識(shí),歡迎關(guān)注我,會(huì)有意想不到的收獲!

          本文由花椒服務(wù)端團(tuán)隊(duì)原創(chuàng)授權(quán)發(fā)布

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

          1. Go對(duì)單元測(cè)試的原生支持

          1.1 testing——Go內(nèi)置的單元測(cè)試庫(kù)。

          要編寫(xiě)一個(gè)新的測(cè)試,需要?jiǎng)?chuàng)建一個(gè)以 _test.go 結(jié)尾的文件,該文件包含 TestXxx 函數(shù)。 將該文件放在與被測(cè)試的包相同的包中。

          通過(guò) go test 命令,能夠自動(dòng)執(zhí)行如下形式的任何函數(shù):

          func TestXxx(*testing.T)
          

          注意:Xxx 可以是任何字母數(shù)字字符串,但是第一個(gè)字母不能是小寫(xiě)字母(一般接被測(cè)試函數(shù)名字,不強(qiáng)求)。傳遞給測(cè)試函數(shù)的參數(shù)是 *testing.T 類(lèi)型。它用于管理測(cè)試狀態(tài)并支持格式化測(cè)試日志。測(cè)試日志會(huì)在執(zhí)行測(cè)試的過(guò)程中不斷累積,并在測(cè)試完成時(shí)轉(zhuǎn)儲(chǔ)至標(biāo)準(zhǔn)輸出。

          詳情參見(jiàn):The-Golang-Standard-Library-by-Example

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

          1.2 TestMain

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

          func TestMain(m *testing.M)
          

          如果測(cè)試文件中包含該函數(shù),那么生成的測(cè)試將調(diào)用 TestMain(m),而不是直接運(yùn)行測(cè)試。

          TestMain 運(yùn)行在主 goroutine 中, 可以在調(diào)用 m.Run 前后做任何設(shè)置和拆卸。注意,在 TestMain 函數(shù)的最后,應(yīng)該使用 m.Run 的返回值作為參數(shù)調(diào)用 os.Exit。

          詳情參見(jiàn):TestMain

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

          1.3 httptest——HTTP測(cè)試輔助工具

          Go 標(biāo)準(zhǔn)庫(kù)專(zhuān)門(mén)提供了 httptest 包專(zhuān)門(mén)用于進(jìn)行 http Web 開(kāi)發(fā)測(cè)試。

          httptest包最關(guān)鍵的是提供了一個(gè) http.ReponseWriter接口的實(shí)現(xiàn)結(jié)構(gòu):httptest.ReponseRecorder,通過(guò)它可以得到一個(gè)http.ReponseWriter,并以此來(lái)接收服務(wù)器返回的響應(yīng)包。

          詳情參見(jiàn):httptest - HTTP 測(cè)試輔助工具

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

          1.4 測(cè)試覆蓋率

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

          詳情參見(jiàn):The cover story

          https://blog.golang.org/cover

          2. 斷言庫(kù)

          Go標(biāo)準(zhǔn)包里并沒(méi)有斷言庫(kù),但是不使用斷言庫(kù)進(jìn)行結(jié)果校驗(yàn)的話(huà),測(cè)試代碼將會(huì)變得非常臃腫,可讀性和可維護(hù)性都會(huì)很差。不過(guò)好在有第三方框架可以讓我們使用。

          2.1 testify

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

          特性:

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

          2.2 gocheck

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

          特性:

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

          詳情參見(jiàn):gocheck 使用介紹

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

          2.3 goconvey

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

          特性:

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

          詳情參見(jiàn):GoConvey框架使用指南

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

          2.4 比較

          其實(shí)gocheck我沒(méi)怎么用過(guò),只是當(dāng)時(shí)調(diào)研的時(shí)候看到了,在斷言方面看起來(lái)和其他的差不多。

          testify和goconvey都有嘗試,最后采用的是goconvey,所以對(duì)goconvey更熟悉一點(diǎn)。

          • testify
          • star數(shù)和活躍度較高:這個(gè)其實(shí)挺重要的,因?yàn)楹芏嗳硕荚谟眠@個(gè)框架的話(huà),這個(gè)框架會(huì)得到更好的完善和發(fā)展,也會(huì)更有生命力。在使用時(shí)碰到的問(wèn)題也可以很方便的在issue中找到答案;
          • testify類(lèi)似于gocheck和gomock的結(jié)合體,但是其mock使用并不是很方便,所以建議還是使用專(zhuān)門(mén)的mock框架。
          • goconvey
          • 可以管理和運(yùn)行測(cè)試用例,通過(guò)嵌套來(lái)體現(xiàn)測(cè)試用例之間的關(guān)系。這是我當(dāng)時(shí)選擇使用goconvey的一個(gè)很重要原因,它可以將測(cè)試代碼組織得更富邏輯性和結(jié)構(gòu)化,提高了測(cè)試代碼的可讀性和可維護(hù)性
          • 支持在web界面進(jìn)行自動(dòng)化編譯測(cè)試。之前在油管上看到一個(gè)博主通過(guò)web界面來(lái)半自動(dòng)化生成測(cè)試代碼。不過(guò)我實(shí)際并沒(méi)有使用這個(gè)功能,感覺(jué)這個(gè)功能適合邏輯簡(jiǎn)單/清楚的代碼。

          3 mock/stub方案

          3.1 識(shí)別依賴(lài)

          普遍來(lái)說(shuō),我們遇到最常見(jiàn)的依賴(lài)無(wú)非下面幾種:

          • 網(wǎng)絡(luò)依賴(lài)——函數(shù)執(zhí)行依賴(lài)于網(wǎng)絡(luò)請(qǐng)求,比如第三方http-api,rpc服務(wù),消息隊(duì)列等
          • 數(shù)據(jù)庫(kù)依賴(lài)
          • I/O依賴(lài)(文件)
          • 還未開(kāi)發(fā)完成的功能模塊

          3.2 mock和stub的區(qū)別

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

          stub本質(zhì)上是對(duì)真實(shí)對(duì)象的一個(gè)模擬,比如調(diào)用者需要一個(gè)值,那就讓stub輸出一個(gè)值,如果調(diào)用者需要傳遞一個(gè)值給stub,那就在stub中定義一個(gè)方法接受該參數(shù),相當(dāng)于“依賴(lài)部分”的一個(gè)簡(jiǎn)化實(shí)現(xiàn)。mock則是在程序代碼中向被測(cè)試代碼注入“依賴(lài)部分”,模擬出函數(shù)調(diào)用返回的結(jié)果。

          個(gè)人認(rèn)為兩者最大的區(qū)別在于依賴(lài)對(duì)象是否和被測(cè)對(duì)象有交互,從結(jié)果來(lái)看,stub不會(huì)使測(cè)試失敗,它只是為被測(cè)對(duì)象提供依賴(lài)的對(duì)象,并不改變測(cè)試結(jié)果,而mock則會(huì)根據(jù)不同的交互測(cè)試要求,很可能會(huì)更改測(cè)試的結(jié)果。stub是state-based,關(guān)注的是輸入和輸出。mock是interaction-based,關(guān)注的是交互過(guò)程。

          mock和stub還有一個(gè)重要的區(qū)別就是expectiation。對(duì)于mock來(lái)說(shuō),expectiation是重中之重:我們期待方法有沒(méi)有被調(diào)用,期待適當(dāng)?shù)膮?shù),期待調(diào)用的次數(shù),甚至期待多個(gè)mock之間的調(diào)用順序。所有的一切期待都是事先準(zhǔn)備好,在測(cè)試過(guò)程中和測(cè)試結(jié)束后驗(yàn)證是否和預(yù)期的一致。而對(duì)于stub,通常都不會(huì)關(guān)注expectiation,沒(méi)有任何代碼來(lái)幫助判斷這個(gè)stub類(lèi)是否被調(diào)用。雖然理論上某些stub實(shí)現(xiàn)也可以通過(guò)自己編碼的方式增加對(duì)expectiation的內(nèi)容,比如增加一個(gè)計(jì)數(shù)器,每次調(diào)用+1之類(lèi),但是實(shí)際上極少這樣做。

          在Go中,如果要用stub,那將是侵入式的,必須將代碼設(shè)計(jì)成可以用stub方法替換的形式。為了測(cè)試,需要專(zhuān)門(mén)用一個(gè)全局變量 來(lái)保存具有外部依賴(lài)的方法。然而在不提倡使用全局變量的Go語(yǔ)言當(dāng)中,這顯然是不合適的。所以,并不提倡這種Stub方式。

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

          詳情參見(jiàn):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

          特性:

          • 可以為全局變量、函數(shù)、過(guò)程打樁
          • 比gomock輕量,不需要依賴(lài)接口

          缺陷:

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

          詳情參見(jiàn):GoStub框架使用指南

          3.4 gomock

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

          特性:

          • golang官方開(kāi)發(fā)維護(hù)的接口級(jí)別的mock方案
          • 包含了GoMock包和mockgen工具兩部分,其中GoMock包完成對(duì)樁對(duì)象生命周期的管理,mockgen工具用來(lái)生成interface對(duì)應(yīng)的Mock類(lèi)源文件。

          缺陷:

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

          詳情參見(jiàn):使用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

          特性:

          • 可以為全局變量、函數(shù)、過(guò)程、方法打樁,同時(shí)避免了gostub對(duì)代碼的侵入

          缺陷:

          • 對(duì)inline函數(shù)打樁無(wú)效
          • 不支持多次調(diào)用樁函數(shù)(方法)而呈現(xiàn)不同行為的復(fù)雜情況

          詳情參見(jiàn):Monkey框架使用指南

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

          3.6 sqlmock

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

          特性:

          • 適用于和數(shù)據(jù)庫(kù)的交互場(chǎng)景。可以創(chuàng)建模擬連接,編寫(xiě)原生sql 語(yǔ)句,編寫(xiě)返回值或者錯(cuò)誤信息并判斷執(zhí)行結(jié)果和預(yù)設(shè)的返回值
          • 提供了完整的事務(wù)的執(zhí)行測(cè)試框架,支持prepare參數(shù)化提交和執(zhí)行的Mock方案
          • 持久層框架底層一般都使用”github.com/go-sql-driver/mysql”,所以一般都能夠使用sqlmock庫(kù)進(jìn)行mock

          缺陷:

          • 因?yàn)槭钦齽t匹配,所以可能漏掉sql的語(yǔ)法錯(cuò)誤
          • 寫(xiě)入后沒(méi)法驗(yàn)證

          3.7 httpexpect

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

          特性:

          • 適用于對(duì)http的clent進(jìn)行測(cè)試,對(duì)服務(wù)端的回包進(jìn)行打樁
          • 支持對(duì)不同方法(get,post,head等)的構(gòu)造,支持自定義返回值json

          sqlmock和httpexpect都蠻簡(jiǎn)單的,看完github主頁(yè)的QuickStart基本就會(huì)用了~~

          4 使用goconvey+gomonkey+sqlmock進(jìn)行測(cè)試

          4.1 選擇原因

          • 外層框架——goconvey。項(xiàng)目代碼很多邏輯比較復(fù)雜,需要編寫(xiě)不同情況下的測(cè)試用例,用goconvey組織的測(cè)試代碼邏輯層次比較清晰,有著較好的可讀性和可維護(hù)性。斷言方面感覺(jué)convey和testify功能差不多。不過(guò)convey沒(méi)有testify社區(qū)活躍度高,后續(xù)使用convey時(shí)碰到一些問(wèn)題,都不太容易找到解決辦法,給作者提issue,感覺(jué)回復(fù)效率也不是很高。
          • 函數(shù)mock——gomonkey。項(xiàng)目代碼基本都不是基于interface實(shí)現(xiàn)的,所以不太方便使用gomock,項(xiàng)目目前運(yùn)行穩(wěn)定,所以也不想因?yàn)閱卧獪y(cè)試重構(gòu)原來(lái)的代碼,所以也不太方便gostub。好在還有g(shù)omonkey可以用,基本符合我們對(duì)函數(shù)打樁的需求。
          • 持久層mock——sqlmock。我們持久層的框架是gorm。當(dāng)時(shí)考慮2種方法進(jìn)行mock,一種是使用gomonkey對(duì)gorm的函數(shù)進(jìn)行mock,另一種則是選用sqlmock。但碰到下圖所示的sql語(yǔ)句,如果使用gomonkey的話(huà)需要對(duì)連續(xù)調(diào)用的gorm函數(shù)都進(jìn)行mock,過(guò)于繁雜。而用sqlmock的話(huà)只需匹配對(duì)應(yīng)的sql語(yǔ)句即可。
          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打開(kāi)一個(gè)新的gorm連接并賦值給數(shù)據(jù)庫(kù)操作實(shí)例

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

          接下來(lái)就和sqlmock的普通使用沒(méi)什么區(qū)別了,只要mock時(shí)能夠成功的匹配gorm生成的sql語(yǔ)句即可

          詳情參見(jiàn):Stub database connection with GORM

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

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

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

          原因:

          • run的時(shí)候會(huì)做編譯器優(yōu)化,調(diào)用A會(huì)直接被優(yōu)化為調(diào)用B(內(nèi)聯(lián))。所以對(duì)前者的patch并沒(méi)有成功。

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

          • 給函數(shù)B也打補(bǔ)丁
          • 在go test時(shí)加參數(shù)來(lái)避免編譯器優(yōu)化內(nèi)聯(lián) go test -gcflags=-l

          5 其他

          5.1 單元測(cè)試的粒度

          對(duì)于剛開(kāi)始做單元測(cè)試的同學(xué)來(lái)說(shuō),如何把握單元測(cè)試的粒度是一個(gè)讓人頭疼的問(wèn)題。

          測(cè)試粒度做的太細(xì),會(huì)耗費(fèi)大量的開(kāi)發(fā)以及維護(hù)時(shí)間,每改一個(gè)方法,都要改動(dòng)其對(duì)應(yīng)的測(cè)試方法。當(dāng)發(fā)生代碼重構(gòu)的時(shí)候那簡(jiǎn)直就是噩夢(mèng)(因?yàn)樗械膯卧獪y(cè)試又都要寫(xiě)一遍了…)。

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

          5.2 單元測(cè)試的成本和收益

          在受益于單元測(cè)試的好處的同時(shí),也必然增加了代碼量以及維護(hù)成本。

          下面這張成本/價(jià)值象限圖清晰闡述了在不同性質(zhì)的系統(tǒng)中單元測(cè)試的成本和價(jià)值之間的關(guān)系。

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

          參考鏈接

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

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

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

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

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


          主站蜘蛛池模板: 国产成人高清视频一区二区| 欧美一区内射最近更新| 精品无人区一区二区三区在线| 日本一区二区三区在线网| 视频在线一区二区三区| 真实国产乱子伦精品一区二区三区| 亚洲国产综合无码一区二区二三区| 久久精品国产一区二区| 制服丝袜一区二区三区| 精品欧洲av无码一区二区14| 国产一区二区三区乱码在线观看| 精彩视频一区二区三区| 一区二区三区无码视频免费福利| 日韩精品无码一区二区三区不卡 | 国产伦理一区二区三区| 日本一区视频在线播放| 久久国产精品视频一区| 黑巨人与欧美精品一区| 中文字幕无码一区二区三区本日 | 精品一区二区三区免费毛片爱 | 久久精品免费一区二区| 国产一区二区三区在线观看影院 | 国产在线视频一区| 无码免费一区二区三区免费播放| 国模无码一区二区三区| 国产亚洲日韩一区二区三区| 国产精品视频一区国模私拍| 色狠狠色噜噜Av天堂一区| 中文字幕乱码一区久久麻豆樱花| 无码日韩精品一区二区人妻| 亚洲色偷偷偷网站色偷一区| 一区二区三区在线观看中文字幕| 人妻体内射精一区二区三四| 夜夜精品无码一区二区三区| 国产美女一区二区三区| 国产亚洲情侣一区二区无码AV| 亚洲欧美成人一区二区三区 | 香蕉视频一区二区| 精品乱码一区二区三区四区| 精品一区二区三区免费观看| 麻豆AV天堂一区二区香蕉|