国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

一文搞懂Go gRPC服務(wù)Handler單元測(cè)試

這篇具有很好參考價(jià)值的文章主要介紹了一文搞懂Go gRPC服務(wù)Handler單元測(cè)試。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

在云原生時(shí)代和微服務(wù)架構(gòu)背景下,HTTP和RPC協(xié)議成為服務(wù)間通信和與客戶端交互的兩種主要方式。對(duì)于Go語言而言,標(biāo)準(zhǔn)庫提供了net/http/httptest包,為開發(fā)人員提供了便捷的方式來構(gòu)建服務(wù)端HTTP Handler單元測(cè)試的測(cè)試腳手架代碼,而無需真正建立HTTP服務(wù)器,讓開發(fā)人員可以聚焦于對(duì)Handler業(yè)務(wù)邏輯的測(cè)試。比如下面這個(gè)示例:

//?grpc-test-examples/httptest/http_handler_test.go

func?myHandler(w?http.ResponseWriter,?r?*http.Request)?{
????//?設(shè)置響應(yīng)頭
????w.Header().Set("Content-Type",?"text/plain")

????//?根據(jù)請(qǐng)求方法進(jìn)行不同的處理
????switch?r.Method?{
????case?http.MethodGet:
????????//?處理GET請(qǐng)求
????????fmt.Fprint(w,?"Hello,?World!")
????...?...
????}
}

func?TestMyHandler(t?*testing.T)?{
????//?創(chuàng)建一個(gè)ResponseRecorder來記錄Handler的響應(yīng)
????rr?:=?httptest.NewRecorder()

????//?創(chuàng)建一個(gè)模擬的HTTP請(qǐng)求,可以指定請(qǐng)求的方法、路徑、正文等
????req,?err?:=?http.NewRequest("GET",?"/path",?nil)
????if?err?!=?nil?{
????????t.Fatal(err)
????}

????//?調(diào)用被測(cè)試的Handler函數(shù),傳入ResponseRecorder和Request對(duì)象
????//?這里假設(shè)被測(cè)試的Handler函數(shù)為myHandler
????myHandler(rr,?req)

????//?檢查響應(yīng)狀態(tài)碼和內(nèi)容
????if?rr.Code?!=?http.StatusOK?{
????????t.Errorf("Expected?status?200;?got?%d",?rr.Code)
????}
????expected?:=?"Hello,?World!"
????if?rr.Body.String()?!=?expected?{
????????t.Errorf("Expected?body?to?be?%q;?got?%q",?expected,?rr.Body.String())
????}
}

注:對(duì)http client端的單元測(cè)試,也可以利用httptest的NewServer來構(gòu)建一個(gè)fake的http server[1]。

然而,對(duì)于使用主流的gRPC等RPC協(xié)議的服務(wù)端Handler[2]來說,是否存在類似httptest的測(cè)試腳手架生成工具包呢?對(duì)gRPC的服務(wù)端Handler有哪些單元測(cè)試的方法呢?在這篇文章中,我們就一起來探究一下。

1. 建立被測(cè)的gRPC服務(wù)端Handler

我們首先來建立一個(gè)涵蓋多種gRPC通信模式的服務(wù)端Handler集合。

gRPC支持四種通信模式,它們分別為:

  • 簡(jiǎn)單RPC(Simple RPC,也稱為Unary RPC)

這是最簡(jiǎn)單的,也是最常用的gRPC通信模式,簡(jiǎn)單來說就是一請(qǐng)求一應(yīng)答。

  • 服務(wù)端流RPC(Server-streaming RPC)

客戶端發(fā)來一個(gè)請(qǐng)求,服務(wù)端通過流返回多個(gè)應(yīng)答。

  • 客戶端流RPC(Client-streaming RPC)

客戶端通過流發(fā)來多個(gè)請(qǐng)求,服務(wù)端以一個(gè)應(yīng)答回復(fù)。

  • 雙向流RPC(Bidirectional-Streaming RPC)

客戶端通過流發(fā)起多個(gè)請(qǐng)求,服務(wù)端也通過流對(duì)應(yīng)返回多個(gè)應(yīng)答。

注:關(guān)于gRPC四種通信方式的詳情,可以參考我之前寫的《gRPC客戶端的那些事兒[3]》一文。

我們這個(gè)SUT(被測(cè)目標(biāo))是包含以上四種通信模式的gRPC服務(wù),它的Protocol Buffers[4]文件如下:

//?grpc-test-examples/grpctest/IDL/proto/mygrpc.proto

syntax?=?"proto3";

package?mygrpc;

service?MyService?{
??//?Unary?RPC
??rpc?UnaryRPC(RequestMessage)?returns?(ResponseMessage)?{}

??//?Server-Streaming?RPC
??rpc?ServerStreamingRPC(RequestMessage)?returns?(stream?ResponseMessage)?{}

??//?Client-Streaming?RPC
??rpc?ClientStreamingRPC(stream?RequestMessage)?returns?(ResponseMessage)?{}

??//?Bidirectional-Streaming?RPC
??rpc?BidirectionalStreamingRPC(stream?RequestMessage)?returns?(stream?ResponseMessage)?{}
}

message?RequestMessage?{
??string?message?=?1;
}

message?ResponseMessage?{
??string?message?=?1;
}

通過protoc,我們可基于上述proto文件生成MyService樁(Stub)代碼,生成的代碼放在了mygrpc目錄下面:

//?grpc-test-examples/grpctest/Makefile

all:?gen

gen:
????protoc?-I?./IDL/proto?mygrpc.proto?--gofast_out=plugins=grpc:./mygrpc

注:你的環(huán)境下需要安裝protoc[5]和protoc-gen-go[6]才能正確執(zhí)行上面生成命令,具體的安裝方法可參考protoc安裝文檔[7]

注:除了使用經(jīng)典的protoc[8]基于proto文件生成Go源碼外,也可以基于Go開發(fā)的buf cli[9]進(jìn)行代碼生成和API管理。buf cLi是現(xiàn)代、快速、高效的Protobuf API管理的終極工具,為基于Protobuf的開發(fā)和維護(hù)提供了全面的解決方案。等有機(jī)會(huì)的時(shí)候,我在以后的文章中詳細(xì)說說buf。

有了生成的樁代碼后,我們便可以建立一個(gè)gRPC服務(wù)器:

//?grpc-test-examples/grpctest/main.go

package?main
??
import?(
????pb?"demo/mygrpc"
????"log"
????"net"

????"google.golang.org/grpc"
)

func?main()?{
????//?創(chuàng)建?gRPC?服務(wù)器
????lis,?err?:=?net.Listen("tcp",?":50051")
????if?err?!=?nil?{
????????log.Fatalf("failed?to?listen:?%v",?err)
????}
????s?:=?grpc.NewServer()

????//?注冊(cè)?MyService?服務(wù)
????pb.RegisterMyServiceServer(s,?&server{})

????//?啟動(dòng)?gRPC?服務(wù)器
????log.Println("Starting?gRPC?server...")
????if?err?:=?s.Serve(lis);?err?!=?nil?{
????????log.Fatalf("failed?to?serve:?%v",?err)
????}
}

我們看到:在main函數(shù)中,我們創(chuàng)建了一個(gè)TCP監(jiān)聽器,并使用grpc.NewServer()創(chuàng)建了一個(gè)gRPC服務(wù)器。然后,我們通過調(diào)用pb.RegisterMyServiceServer()將server類型的實(shí)例注冊(cè)到gRPC服務(wù)器上,以處理來自客戶端的請(qǐng)求。最后,我們啟動(dòng)gRPC服務(wù)器并監(jiān)聽指定的端口。

上面代碼中注冊(cè)到服務(wù)器中的server類型就是實(shí)現(xiàn)了MyService服務(wù)接口的具體類型,它實(shí)現(xiàn)了MyService定義的所有方法:

//?grpc-test-examples/grpctest/server.go

package?main

import?(
?"context"
?"fmt"
?"strconv"

?pb?"demo/mygrpc"
)

type?server?struct{}

func?(s?*server)?UnaryRPC(ctx?context.Context,?req?*pb.RequestMessage)?(*pb.ResponseMessage,?error)?{
?message?:=?"Unary?RPC?received:?"?+?req.Message
?fmt.Println(message)

?return?&pb.ResponseMessage{
??Message:?"Unary?RPC?response",
?},?nil
}

func?(s?*server)?ServerStreamingRPC(req?*pb.RequestMessage,?stream?pb.MyService_ServerStreamingRPCServer)?error?{
?message?:=?"Server?Streaming?RPC?received:?"?+?req.Message
?fmt.Println(message)

?for?i?:=?0;?i?<?5;?i++?{
??response?:=?&pb.ResponseMessage{
???Message:?"Server?Streaming?RPC?response?"?+?strconv.Itoa(i+1),
??}
??if?err?:=?stream.Send(response);?err?!=?nil?{
???return?err
??}
?}

?return?nil
}

func?(s?*server)?ClientStreamingRPC(stream?pb.MyService_ClientStreamingRPCServer)?error?{
?var?messages?[]string

?for?{
??req,?err?:=?stream.Recv()
??if?err?!=?nil?{
???return?err
??}

??messages?=?append(messages,?req.Message)

??if?req.Message?==?"end"?{
???break
??}
?}

?message?:=?"Client?Streaming?RPC?received:?"?+?fmt.Sprintf("%v",?messages)
?fmt.Println(message)

?return?stream.SendAndClose(&pb.ResponseMessage{
??Message:?"Client?Streaming?RPC?response",
?})
}

func?(s?*server)?BidirectionalStreamingRPC(stream?pb.MyService_BidirectionalStreamingRPCServer)?error?{
?for?{
??req,?err?:=?stream.Recv()
??if?err?!=?nil?{
???return?err
??}

??message?:=?"Bidirectional?Streaming?RPC?received:?"?+?req.Message
??fmt.Println(message)

??response?:=?&pb.ResponseMessage{
???Message:?"Bidirectional?Streaming?RPC?response",
??}
??if?err?:=?stream.Send(response);?err?!=?nil?{
???return?err
??}
?}
}

在上面代碼中,我們創(chuàng)建了一個(gè)server結(jié)構(gòu)體類型,并實(shí)現(xiàn)了MyService的所有RPC方法。每個(gè)方法都接收相應(yīng)的請(qǐng)求消息,并返回對(duì)應(yīng)的響應(yīng)消息。我們的目標(biāo)僅是演示如何對(duì)上述gRPC Handler進(jìn)行單元測(cè)試,所以這里的實(shí)現(xiàn)邏輯非常簡(jiǎn)單。

接下來,我們就來逐一對(duì)這些gRPC的Handler方法進(jìn)行單測(cè),我們先從簡(jiǎn)單的UnaryRPC方法開始。

2. Unary RPC Handler的單元測(cè)試

Unary RPC是最簡(jiǎn)單,也是最容易理解的RPC通信模式,即客戶端與服務(wù)端采用一請(qǐng)求一應(yīng)答的模式。server類型的UnaryRPC Handler方法的原型如下:

//?grpc-test-examples/grpctest/server.go

func?(s?*server)?UnaryRPC(ctx?context.Context,?req?*pb.RequestMessage)?(*pb.ResponseMessage,?error)

就像文章開頭做的那個(gè)httpserver的handler單測(cè)一樣,我們肯定不想真實(shí)啟動(dòng)一個(gè)gRPC server,也不想測(cè)試gRPC服務(wù)器本身。我們只想測(cè)試服務(wù)端handler方法的邏輯是否正確。

觀察一下這個(gè)方法原型,我們發(fā)現(xiàn)它僅依賴兩個(gè)消息結(jié)構(gòu):RequestMessage和ResponseMessage,這兩個(gè)消息結(jié)構(gòu)是上面基于proto文件自動(dòng)生成的,這樣我們就可以不借助任何工具包實(shí)現(xiàn)對(duì)UnaryRPC handler方法的單測(cè),也無需啟動(dòng)真實(shí)的gRPC Server:

//?grpc-test-examples/grpctest/server_test.go

type?server?struct{}

func?TestServerUnaryRPC(t?*testing.T)?{
????s?:=?&server{}

????req?:=?&pb.RequestMessage{
????????Message:?"Test?message",
????}

????resp,?err?:=?s.UnaryRPC(context.Background(),?req)
????if?err?!=?nil?{
????????t.Fatalf("UnaryRPC?failed:?%v",?err)
????}

????expectedResp?:=?&pb.ResponseMessage{
????????Message:?"Unary?RPC?response",
????}

????if?resp.Message?!=?expectedResp.Message?{
????????t.Errorf("Unexpected?response.?Got:?%s,?Want:?%s",?resp.Message,?expectedResp.Message)
????}
}

將其改造為基于subtest[10]和表驅(qū)動(dòng)的測(cè)試也非常easy:

//?grpc-test-examples/grpctest/server_test.go

func?TestServerUnaryRPCs(t?*testing.T)?{
?tests?:=?[]struct?{
??name???????????string
??requestMessage?*pb.RequestMessage
??expectedResp???*pb.ResponseMessage
?}{
??{
???name:?"Test?Case?1",
???requestMessage:?&pb.RequestMessage{
????Message:?"Test?message",
???},
???expectedResp:?&pb.ResponseMessage{
????Message:?"Unary?RPC?response",
???},
??},
??//?Add?more?test?cases?as?needed
?}

?s?:=?&server{}

?for?_,?tt?:=?range?tests?{
??t.Run(tt.name,?func(t?*testing.T)?{
???resp,?err?:=?s.UnaryRPC(context.Background(),?tt.requestMessage)
???if?err?!=?nil?{
????t.Fatalf("UnaryRPC?failed:?%v",?err)
???}

???if?resp.Message?!=?tt.expectedResp.Message?{
????t.Errorf("Unexpected?response.?Got:?%s,?Want:?%s",?resp.Message,?tt.expectedResp.Message)
???}
??})
?}
}

如果gRPC handler測(cè)試都像UnaryRPC這樣簡(jiǎn)單那就好了,但實(shí)際上...,好吧,我們繼續(xù)向下看就好了。

3. 針對(duì)Streaming通信模式的單元測(cè)試

3.1 ServerStreamingRPC的測(cè)試

前面說過,gRPC支持三種Streaming通信模式:Server-Streaming RPC、Client-Streaming RPC和Bidirectional-Streaming RPC。

我們先來看看Server-Streaming RPC的方法原型:

//?grpc-test-examples/grpctest/server.go
func?(s?*server)?ServerStreamingRPC(req?*pb.RequestMessage,?stream?pb.MyService_ServerStreamingRPCServer)?error

我們看到除了RequestMessag外,該方法還依賴一個(gè)MyService_ServerStreamingRPCServer的類型,這個(gè)類型是一個(gè)接口類型:

//?grpc-test-examples/mygrpc/mygrpc.pb.go

type?MyService_ServerStreamingRPCServer?interface?{
????Send(*ResponseMessage)?error
????grpc.ServerStream
}

到這里,你腦子中可能已經(jīng)冒出了一個(gè)想法:使用fake object來對(duì)ServerStreamingRPC進(jìn)行單測(cè)[11],這的確是一個(gè)可行的方法,我們下面就基于這個(gè)思路實(shí)現(xiàn)一下。

注:關(guān)于基于fake object進(jìn)行單測(cè)的內(nèi)容,大家可以看看我以前寫的一篇文章《[]單測(cè)時(shí)盡量用fake object(https://tonybai.com/2023/04/20/provide-fake-object-for-external-collaborators)》。

3.2 基于fake object的測(cè)試

我們首先創(chuàng)建一個(gè)實(shí)現(xiàn)MyService_ServerStreamingRPCServer的fake object用以代替真實(shí)運(yùn)行RPC服務(wù)器時(shí)由服務(wù)器傳入的stream object:

//?grpc-test-examples/grpctest/server_with_fakeobject_test.go

import?(
????"testing"

????pb?"demo/mygrpc"

????"google.golang.org/grpc"
)

type?fakeServerStreamingRPCStream?struct?{
????grpc.ServerStream
????responses?[]*pb.ResponseMessage
}

func?(m?*fakeServerStreamingRPCStream)?Send(resp?*pb.ResponseMessage)?error?{
????m.responses?=?append(m.responses,?resp)
????return?nil
}

我們看到fakeServerStreamingRPCStream的Send方法只是將收到的ResponseMessage追加到且內(nèi)部的ResponseMessage切片中。

接下來我們?yōu)镾erverStreamingRPC編寫測(cè)試用例:

//?grpc-test-examples/grpctest/server_with_fakeobject_test.go

func?TestServerServerStreamingRPC(t?*testing.T)?{??
????s?:=?&server{}??
??
????req?:=?&pb.RequestMessage{??
????????Message:?"Test?message",??
????}??
??
????stream?:=?&fakeServerStreamingRPCStream{}??
??
????err?:=?s.ServerStreamingRPC(req,?stream)??
????if?err?!=?nil?{??
????????t.Fatalf("ServerStreamingRPC?failed:?%v",?err)??
????}??
???????????????????????????????????????????????????????????????????????????????????????????????????????????????
????expectedResponses?:=?[]string{?????????????????????????????????????????????????????????????????????????????
????????"Server?Streaming?RPC?response?1",?????????????????????????????????????????????????????????????????????
????????"Server?Streaming?RPC?response?2",?????????????????????????????????????????????????????????????????????
????????"Server?Streaming?RPC?response?3",?????????????????????????????????????????????????????????????????????
????????"Server?Streaming?RPC?response?4",?????????????????????????????????????????????????????????????????????
????????"Server?Streaming?RPC?response?5",?????????????????????????????????????????????????????????????????????
????}??????????????????????????????????????????????????????????????????????????????????????????????????????????
???????????????????????????????????????????????????????????????????????????????????????????????????????????????
????if?len(stream.responses)?!=?len(expectedResponses)?{???????????????????????????????????????????????????????
????????t.Errorf("Unexpected?number?of?responses.?Got:?%d,?Want:?%d",?len(stream.responses),?len(expectedResponses))??
????}??????????????????????????????????????????????????????????????????????????????????????????????????????????
???????????????????????????????????????????????????????????????????????????????????????????????????????????????
????for?i,?resp?:=?range?stream.responses?{????????????????????????????????????????????????????????????????????
????????if?resp.Message?!=?expectedResponses[i]?{?????????
????????????t.Errorf("Unexpected?response?at?index?%d.?Got:?%s,?Want:?%s",?i,?resp.Message,?expectedResponses[i])?????????
????????}?????????????????????????????????????????????????????????????????????????????????????????????????????????
????}??
}

在這個(gè)測(cè)試中,ServerStreamingRPC接收一個(gè)請(qǐng)求(req),并通過fake stream object的Send方法返回了5個(gè)response,通過與預(yù)期的response對(duì)比,即可做出測(cè)試是否通過的斷言。

到這里,我們看到:fake object完全滿足對(duì)gRPC Server Handler進(jìn)行測(cè)試的要求。不過我們需要針對(duì)不同的Handler建立不同的fake object類型,和文初基于httptest創(chuàng)建的測(cè)試用例相比,用例間欠缺了一些一致性。

那grpc-go是否提供了類似httptest的工具來幫助我們更一致的實(shí)現(xiàn)grpc server handler的測(cè)試用例呢?我們繼續(xù)往下看。

3.3 利用grpc-go提供的測(cè)試工具包

grpc-go項(xiàng)目在test下提供了bufconn包,可以幫助我們像httptest那樣建立用于測(cè)試的“虛擬gRPC服務(wù)器”,下面是基于bufconn包建立gRPC測(cè)試用服務(wù)器的代碼:

//?grpc-test-examples/grpctest/server_with_buffconn_test.go

package?main

import?(
?"context"
?"log"
?"net"
?"testing"

?pb?"demo/mygrpc"

?"google.golang.org/grpc"
?"google.golang.org/grpc/test/bufconn"
)

func?newGRPCServer(t?*testing.T)?(pb.MyServiceClient,?func())?{
?//?創(chuàng)建?bufconn.Listener?作為服務(wù)器的監(jiān)聽器
?listener?:=?bufconn.Listen(1024?*?1024)

?//?創(chuàng)建?gRPC?服務(wù)器
?srv?:=?grpc.NewServer()

?//?注冊(cè)服務(wù)處理程序
?pb.RegisterMyServiceServer(srv,?&server{})

?//?在監(jiān)聽器上啟動(dòng)服務(wù)器
?go?func()?{
??if?err?:=?srv.Serve(listener);?err?!=?nil?{
???t.Fatalf("Server?failed?to?start:?%v",?err)
??}
?}()

?//?創(chuàng)建?bufconn.Dialer?作為客戶端連接
?dialer?:=?func(context.Context,?string)?(net.Conn,?error)?{
??return?listener.Dial()
?}

?//?使用?DialContext?和?bufconn.Dialer?創(chuàng)建客戶端連接
?conn,?err?:=?grpc.DialContext(context.Background(),?"bufnet",?grpc.WithContextDialer(dialer),?grpc.WithInsecure())
?if?err?!=?nil?{
??t.Fatalf("Failed?to?dial?server:?%v",?err)
?}

?//?創(chuàng)建客戶端實(shí)例
?client?:=?pb.NewMyServiceClient(conn)
?return?client,?func()?{
??err?:=?listener.Close()
??if?err?!=?nil?{
???log.Printf("error?closing?listener:?%v",?err)
??}
??srv.Stop()
?}
}

newGRPCServer是一個(gè)用于在測(cè)試中創(chuàng)建gRPC服務(wù)器和客戶端的輔助函數(shù),它使用bufconn.Listen創(chuàng)建一個(gè)bufconn.Listener作為服務(wù)器的監(jiān)聽器。bufconn包提供了一種在內(nèi)存中模擬網(wǎng)絡(luò)連接的方法。然后,它使用grpc.NewServer()創(chuàng)建了一個(gè)新的gRPC服務(wù)器實(shí)例,并使用pb.RegisterMyServiceServer將待測(cè)的服務(wù)實(shí)例(這里是server類型實(shí)例)注冊(cè)到gRPC服務(wù)器中。接下來,它創(chuàng)建了與該服務(wù)器建連的gRPC客戶端,由于該客戶端要與bufconn.Listener建連,這里用了一個(gè)dialer函數(shù),該函數(shù)將通過調(diào)用listener.Dial()來建立與服務(wù)器的連接。之后基于該連接,我們創(chuàng)建了MyServiceClient的客戶端實(shí)例,并返回,供測(cè)試用例使用。

基于newGPRCServer這種方式,我們改造一下UnaryRPC的測(cè)試用例:

//?grpc-test-examples/grpctest/server_with_buffconn_test.go

func?TestServerUnaryRPCWithBufConn(t?*testing.T)?{
?client,?shutdown?:=?newGRPCServer(t)
?defer?shutdown()

?tests?:=?[]struct?{
??name???????????string
??requestMessage?*pb.RequestMessage
??expectedResp???*pb.ResponseMessage
?}{
??{
???name:?"Test?Case?1",
???requestMessage:?&pb.RequestMessage{
????Message:?"Test?message",
???},
???expectedResp:?&pb.ResponseMessage{
????Message:?"Unary?RPC?response",
???},
??},
??//?Add?more?test?cases?as?needed
?}

?for?_,?tt?:=?range?tests?{
??t.Run(tt.name,?func(t?*testing.T)?{
???resp,?err?:=?client.UnaryRPC(context.Background(),?tt.requestMessage)
???if?err?!=?nil?{
????t.Fatalf("UnaryRPC?failed:?%v",?err)
???}

???if?resp.Message?!=?tt.expectedResp.Message?{
????t.Errorf("Unexpected?response.?Got:?%s,?Want:?%s",?resp.Message,?tt.expectedResp.Message)
???}
??})
?}
}

我們看到,相對(duì)于前面的TestServerUnaryRPCs,兩者復(fù)雜度在一個(gè)層次。如果結(jié)合下面的ServerStreamRPC的測(cè)試用例,你就能看出這種方式在測(cè)試用例一致性方面的優(yōu)勢(shì)了:

//?grpc-test-examples/grpctest/server_with_buffconn_test.go

func?TestServerServerStreamingRPCWithBufConn(t?*testing.T)?{
?client,?shutdown?:=?newGRPCServer(t)
?defer?shutdown()

?req?:=?&pb.RequestMessage{
??Message:?"Test?message",
?}

?stream,?err?:=?client.ServerStreamingRPC(context.Background(),?req)
?if?err?!=?nil?{
??t.Fatalf("ServerStreamingRPC?failed:?%v",?err)
?}

?expectedResponses?:=?[]string{
??"Server?Streaming?RPC?response?1",
??"Server?Streaming?RPC?response?2",
??"Server?Streaming?RPC?response?3",
??"Server?Streaming?RPC?response?4",
??"Server?Streaming?RPC?response?5",
?}

?gotResponses?:=?[]string{}

?for?{
??resp,?err?:=?stream.Recv()
??if?err?!=?nil?{
???break
??}
??gotResponses?=?append(gotResponses,?resp.Message)
?}

?if?len(gotResponses)?!=?len(expectedResponses)?{
??t.Errorf("Unexpected?number?of?responses.?Got:?%d,?Want:?%d",?len(gotResponses),?len(expectedResponses))
?}

?for?i,?resp?:=?range?gotResponses?{
??if?resp?!=?expectedResponses[i]?{
???t.Errorf("Unexpected?response?at?index?%d.?Got:?%s,?Want:?%s",?i,?resp,?expectedResponses[i])
??}
?}
}

我們?cè)僖矡o需為每個(gè)Server Handler建立各自的fake object了!

由此看到:grpc-go的test/bufconn就是類似httptest的那個(gè)grpc server handler的測(cè)試腳手架搭建工具。

3.4 其他Streaming模式的Handler測(cè)試

有了bufconn這一利器,其他Streaming模式的Handler測(cè)試實(shí)現(xiàn)邏輯就大同小異了。本文示例中的ClientStreamingRPC和BidirectionalStreamingRPC兩個(gè)Handler的測(cè)試用例就作為作業(yè),交給各位讀者去完成吧!

4. 小結(jié)

在本文中,我們?cè)敿?xì)探討了如何對(duì)gRPC服務(wù)端Handler進(jìn)行單元測(cè)試,我們的目標(biāo)是找到像net/http/httptest包那樣的,可以為gRPC服務(wù)端handler測(cè)試提供腳手架代碼幫助的測(cè)試方法。

我們按照gRPC的四種通信方式,由簡(jiǎn)到難的逐一探討各種Handler的單測(cè)方法。UnaryRPC handler測(cè)試最為簡(jiǎn)單,毫無技巧的普通測(cè)試邏輯便能應(yīng)付。

但一旦涉及streaming通信方式的測(cè)試,我們就需要借助類似fake object的單測(cè)技術(shù)了。但fake object也有不足,那就是需要為每個(gè)RPC handler建立單獨(dú)的fake object,費(fèi)時(shí)費(fèi)力還缺少一致性!

好在,grpc-go項(xiàng)目為我們提供了test/bufconn包,該包可以像net/http/httptest包那樣幫助我們快速建立可復(fù)用的測(cè)試腳手架代碼,這樣我們便可以為所有服務(wù)端RPC Handler建立一致、穩(wěn)定的單元測(cè)試用例了!

當(dāng)然,服務(wù)端RPC Handler的單測(cè)方法可能不止文中提及這些,各位讀者如果有更好的方法和實(shí)踐,歡迎在評(píng)論區(qū)留言!

本文涉及的源碼可以在這里[12]下載。

5. 參考資料

  • Testing gRPC methods[13]?- https://medium.com/@johnsiilver/testing-grpc-methods-6a8edad4159d

  • 《gRPC Up and Running》[14]?- https://book.douban.com/subject/34796013/

  • Mocking the Universe: Two Techniques for Testing gRPC with Mocks[15]?- https://rotational.io/blog/mocking-the-universe/


“Gopher部落”知識(shí)星球[16]旨在打造一個(gè)精品Go學(xué)習(xí)和進(jìn)階社群!高品質(zhì)首發(fā)Go技術(shù)文章,“三天”首發(fā)閱讀權(quán),每年兩期Go語言發(fā)展現(xiàn)狀分析,每天提前1小時(shí)閱讀到新鮮的Gopher日?qǐng)?bào),網(wǎng)課、技術(shù)專欄、圖書內(nèi)容前瞻,六小時(shí)內(nèi)必答保證等滿足你關(guān)于Go語言生態(tài)的所有需求!2023年,Gopher部落將進(jìn)一步聚焦于如何編寫雅、地道、可讀、可測(cè)試的Go代碼,關(guān)注代碼質(zhì)量并深入理解Go核心技術(shù),并繼續(xù)加強(qiáng)與星友的互動(dòng)。歡迎大家加入!

一文搞懂Go gRPC服務(wù)Handler單元測(cè)試,golang,單元測(cè)試,開發(fā)語言,后端一文搞懂Go gRPC服務(wù)Handler單元測(cè)試,golang,單元測(cè)試,開發(fā)語言,后端

一文搞懂Go gRPC服務(wù)Handler單元測(cè)試,golang,單元測(cè)試,開發(fā)語言,后端一文搞懂Go gRPC服務(wù)Handler單元測(cè)試,golang,單元測(cè)試,開發(fā)語言,后端

著名云主機(jī)服務(wù)廠商DigitalOcean發(fā)布最新的主機(jī)計(jì)劃,入門級(jí)Droplet配置升級(jí)為:1 core CPU、1G內(nèi)存、25G高速SSD,價(jià)格5$/月。有使用DigitalOcean需求的朋友,可以打開這個(gè)鏈接地址[17]:https://m.do.co/c/bff6eed92687 開啟你的DO主機(jī)之路。

Gopher Daily(Gopher每日新聞) - https://gopherdaily.tonybai.com

我的聯(lián)系方式:

  • 微博(暫不可用):https://weibo.com/bigwhite20xx

  • 微博2:https://weibo.com/u/6484441286

  • 博客:tonybai.com

  • github: https://github.com/bigwhite

  • Gopher Daily歸檔 - https://github.com/bigwhite/gopherdaily

一文搞懂Go gRPC服務(wù)Handler單元測(cè)試,golang,單元測(cè)試,開發(fā)語言,后端

商務(wù)合作方式:撰稿、出書、培訓(xùn)、在線課程、合伙創(chuàng)業(yè)、咨詢、廣告合作。

參考資料

[1]?

利用httptest的NewServer來構(gòu)建一個(gè)fake的http server:?https://tonybai.com/2023/04/20/provide-fake-object-for-external-collaborators

[2]?

gRPC等RPC協(xié)議的服務(wù)端Handler:?https://tonybai.com/2021/09/26/the-design-of-the-response-for-grpc-server

[3]?

gRPC客戶端的那些事兒:?https://tonybai.com/2021/09/17/those-things-about-grpc-client

[4]?

Protocol Buffers:?https://protobuf.dev/

[5]?

protoc:?https://grpc.io/docs/protoc-installation/

[6]?

protoc-gen-go:?https://github.com/golang/protobuf/tree/master/protoc-gen-go

[7]?

protoc安裝文檔:?https://grpc.io/docs/protoc-installation/

[8]?

protoc:?https://grpc.io/docs/protoc-installation/

[9]?

buf cli:?https://github.com/bufbuild/buf

[10]?

基于subtest:?https://tonybai.com/2023/03/15/an-intro-of-go-subtest/

[11]?

使用fake object來對(duì)ServerStreamingRPC進(jìn)行單測(cè):?https://tonybai.com/2023/04/20/provide-fake-object-for-external-collaborators/

[12]?

這里:?https://github.com/bigwhite/experiments/tree/master/grpc-test-examples

[13]?

Testing gRPC methods:?https://medium.com/@johnsiilver/testing-grpc-methods-6a8edad4159d

[14]?

《gRPC Up and Running》:?https://book.douban.com/subject/34796013/

[15]?

Mocking the Universe: Two Techniques for Testing gRPC with Mocks:?https://rotational.io/blog/mocking-the-universe/

[16]?

“Gopher部落”知識(shí)星球:?https://public.zsxq.com/groups/51284458844544

[17]?

鏈接地址:?https://m.do.co/c/bff6eed92687文章來源地址http://www.zghlxwxcb.cn/news/detail-766852.html

到了這里,關(guān)于一文搞懂Go gRPC服務(wù)Handler單元測(cè)試的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • [golang 微服務(wù)] 4.  gRPC介紹,Protobuf結(jié)合gRPC 創(chuàng)建微服務(wù)

    [golang 微服務(wù)] 4. gRPC介紹,Protobuf結(jié)合gRPC 創(chuàng)建微服務(wù)

    gRPC是一個(gè) 高性能 、 開源 和 通用 的 RPC 框架 , 面向移動(dòng)端 和 HTTP/2 設(shè)計(jì),目前提供 C、Java 和 Go語言版本,分別是:grpc, grpc-java, grpc-go, 其中 C 版本支持 C, C++, Node.js, Python, Ruby, Objective-C, PHP 和 C# 支持 (1).提供幾乎所有主流語言的實(shí)現(xiàn), 打破語言隔閡 (2). 基于 HTTP/2 標(biāo)準(zhǔn)設(shè)計(jì)

    2024年02月04日
    瀏覽(19)
  • golang 工程組件:grpc-gateway 環(huán)境安裝+默認(rèn)網(wǎng)關(guān)測(cè)試

    golang 工程組件:grpc-gateway 環(huán)境安裝+默認(rèn)網(wǎng)關(guān)測(cè)試

    grpc-gateway 顧名思義是專門是grpc的網(wǎng)關(guān)。也是一個(gè)protobuf的編譯器,是一個(gè)proto的插件。 grpc-gateway就是將http請(qǐng)求處理后轉(zhuǎn)發(fā)到對(duì)應(yīng)grpc服務(wù)上。很多瀏覽器,或者客戶端開箱不支持grpc,只支持傳統(tǒng)的restful API。 grpc網(wǎng)關(guān)而且也支持負(fù)載,兼容不同版本。 官方文檔 grpc-gateway 源碼

    2024年02月08日
    瀏覽(23)
  • [golang 微服務(wù)] 6. GRPC微服務(wù)集群+Consul集群+grpc-consul-resolver案例演示

    [golang 微服務(wù)] 6. GRPC微服務(wù)集群+Consul集群+grpc-consul-resolver案例演示

    上一節(jié)講解了consul集群: [golang 微服務(wù)] 5. 微服務(wù)服務(wù)發(fā)現(xiàn)介紹,安裝以及consul的使用,Consul集群,這樣的話,當(dāng)一臺(tái)server掛掉之后,集群就會(huì)從另一臺(tái)server中獲取服務(wù),這就保證了客戶端訪問consul集群的負(fù)載均衡性. 這里還有一個(gè)問題: 就是當(dāng)終端的對(duì)應(yīng)的 微服務(wù)掛掉 了,consul集群se

    2024年02月09日
    瀏覽(21)
  • 一文搞懂性能測(cè)試

    一文搞懂性能測(cè)試

    我們經(jīng)??吹降男阅軠y(cè)試概念,有人或稱之為性能策略,或稱之為性能方法,或稱之為性能場(chǎng)景分類,大概可以看到性能測(cè)試、負(fù)載測(cè)試、壓力測(cè)試、強(qiáng)度測(cè)試等一堆專有名詞的解釋。 針對(duì)這些概念,我不知道你看到的時(shí)候會(huì)不會(huì)像我的感覺一樣:亂!一個(gè)小小的性能測(cè)試,

    2024年02月08日
    瀏覽(23)
  • 從零開始構(gòu)建gRPC的Go服務(wù)

    從零開始構(gòu)建gRPC的Go服務(wù)

    Protocol Buffers and gRPC是用于定義通過網(wǎng)絡(luò)有效通信的微服務(wù)的流行技術(shù)。許多公司在Go中構(gòu)建gRPC微服務(wù),發(fā)布了他們開發(fā)的框架,本文將從gRPC入門開始,一步一步構(gòu)建一個(gè)gRPC服務(wù)。 之前在B站看過一個(gè)gRPC教學(xué)視頻,嘗試跟著視頻做但踩了不少的坑,因此決定自己動(dòng)手從官方教

    2024年04月17日
    瀏覽(21)
  • 一文1000字徹底搞懂Web測(cè)試與App測(cè)試的區(qū)別

    總結(jié)分享一些項(xiàng)目需要結(jié)合Web測(cè)試和App測(cè)試的工作經(jīng)驗(yàn)給大家: 從功能測(cè)試區(qū)分,Web測(cè)試與App測(cè)試在測(cè)試用例設(shè)計(jì)和測(cè)試流程上沒什么區(qū)別。 而兩者的主要區(qū)別體現(xiàn)在如下幾個(gè)方面: Web項(xiàng)目,B/S架構(gòu),基于瀏覽器的;Web測(cè)試過程中,客戶端會(huì)隨服務(wù)器端同步更新,所以只需

    2024年02月08日
    瀏覽(19)
  • 【gRPC實(shí)現(xiàn)java端調(diào)用go的服務(wù)】

    【gRPC實(shí)現(xiàn)java端調(diào)用go的服務(wù)】

    在lib下面的存在一個(gè)simple.proto文件,我們使用插件protobuf-maven-plugin對(duì)其進(jìn)行編譯。配置如下: 利用插件進(jìn)行編譯 后可以獲得對(duì)應(yīng)的文件。 在client下創(chuàng)建一個(gè)grpc的包,并將以上兩個(gè)文件放入。最后創(chuàng)建一個(gè)SimpleClient。 最后在Application中調(diào)用即可

    2024年02月04日
    瀏覽(23)
  • Golang單元測(cè)試和壓力測(cè)試

    Golang單元測(cè)試和壓力測(cè)試

    ? ? ? ? go語言中的測(cè)試依賴go test命令。編寫測(cè)試代碼和編寫普通的Go代碼過程類似,并不需要學(xué)習(xí)新的語法,規(guī)則和工具。 ? ? ? ? go test命令是一個(gè)按照一定約定和組織的測(cè)試代碼的驅(qū)動(dòng)程序。在包目錄內(nèi),所有以_test.go為后綴名的源代碼文件都是go test測(cè)試的一部分,不會(huì)

    2024年04月27日
    瀏覽(21)
  • Golang單元測(cè)試詳解(一):?jiǎn)卧獪y(cè)試的基本使用方法

    Golang 中的單元測(cè)試是使用標(biāo)準(zhǔn)庫 testing 來實(shí)現(xiàn)的,編寫一個(gè)單元測(cè)試是很容易的: 創(chuàng)建測(cè)試文件:在 Go 項(xiàng)目的源代碼目錄下創(chuàng)建一個(gè)新的文件(和被測(cè)代碼文件在同一個(gè)包),以 _test.go 為后綴名。例如,要測(cè)試net包中 dial.go 中的方法,在 net 包中創(chuàng)建一個(gè)名字為 dial_test.g

    2024年02月06日
    瀏覽(17)
  • Go gRPC etcd實(shí)現(xiàn)服務(wù)注冊(cè)發(fā)現(xiàn)與負(fù)載均衡

    Go gRPC etcd實(shí)現(xiàn)服務(wù)注冊(cè)發(fā)現(xiàn)與負(fù)載均衡

    如果不了解go + grpc 調(diào)用方式和實(shí)現(xiàn)細(xì)節(jié),可以參考上一篇文章 golang grpc配置使用實(shí)戰(zhàn)教程 技術(shù)點(diǎn) 版本 描述 golang 1.19 基礎(chǔ)版本 grpc v1.41.0 gRPC golang包 etcd server 3.5.0 注冊(cè)中心 etcd client v3.5.8 客戶端服務(wù)發(fā)現(xiàn)和負(fù)載均衡 服務(wù)注冊(cè)依賴etcd的 key-value操作,key作為gRPC服務(wù)唯一標(biāo)識(shí),

    2024年02月05日
    瀏覽(37)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包