feat: add rpc methods test

This commit is contained in:
zhuyasen
2022-12-01 23:35:44 +08:00
parent 4fdabb148c
commit 6130579138
9 changed files with 266 additions and 35 deletions

View File

@@ -195,6 +195,7 @@ func saveFile(moduleName string, serverName string, out string, filePath string,
content = bytes.ReplaceAll(content, []byte("moduleNameExample"), []byte(moduleName))
content = bytes.ReplaceAll(content, []byte("serverNameExample"), []byte(serverName))
content = bytes.ReplaceAll(content, firstLetterToUpper("serverNameExample"), firstLetterToUpper(serverName))
return os.WriteFile(file, content, 0666)
}
@@ -220,3 +221,11 @@ func isExists(path string) bool {
}
return true
}
func firstLetterToUpper(s string) []byte {
if s == "" {
return []byte{}
}
return []byte(strings.ToUpper(s[:1]) + s[1:])
}

View File

@@ -9,16 +9,17 @@ import (
)
// GenerateFiles generate service template code and error codes
func GenerateFiles(file *protogen.File) ([]byte, []byte) {
func GenerateFiles(file *protogen.File) ([]byte, []byte, []byte) {
if len(file.Services) == 0 {
return nil, nil
return nil, nil, nil
}
pss := parse.GetServices(file)
serviceTmplContent := genServiceTmplFile(pss)
serviceTestTmplContent := genServiceTestTmplFile(pss)
errCodeFileContent := genErrCodeFile(pss)
return serviceTmplContent, errCodeFileContent
return serviceTmplContent, serviceTestTmplContent, errCodeFileContent
}
func genServiceTmplFile(fields []*parse.PbService) []byte {
@@ -26,6 +27,11 @@ func genServiceTmplFile(fields []*parse.PbService) []byte {
return lf.execute()
}
func genServiceTestTmplFile(pbs []*parse.PbService) []byte {
lf := &serviceTestTmplFields{PbServices: pbs}
return lf.execute()
}
func genErrCodeFile(fields []*parse.PbService) []byte {
cf := &errCodeFields{PbServices: fields}
return cf.execute()
@@ -43,6 +49,18 @@ func (f *serviceTmplFields) execute() []byte {
return buf.Bytes()
}
type serviceTestTmplFields struct {
PbServices []*parse.PbService
}
func (f *serviceTestTmplFields) execute() []byte {
buf := new(bytes.Buffer)
if err := serviceLogicTestTmpl.Execute(buf, f); err != nil {
panic(err)
}
return buf.Bytes()
}
type errCodeFields struct {
PbServices []*parse.PbService
}

View File

@@ -12,6 +12,10 @@ func init() {
if err != nil {
panic(err)
}
serviceLogicTestTmpl, err = template.New("serviceLogicTestTmpl").Parse(serviceLogicTestTmplRaw)
if err != nil {
panic(err)
}
rpcErrCodeTmpl, err = template.New("rpcErrCode").Parse(rpcErrCodeTmplRaw)
if err != nil {
panic(err)
@@ -86,6 +90,147 @@ func (s *{{.LowerServiceName}}) {{.MethodName}}(ctx context.Context, req *server
{{- end}}
{{- end}}
`
serviceLogicTestTmpl *template.Template
serviceLogicTestTmplRaw = `package service
import (
"context"
"fmt"
"testing"
"time"
serverNameExampleV1 "moduleNameExample/api/serverNameExample/v1"
"moduleNameExample/configs"
"moduleNameExample/internal/config"
"github.com/zhufuyi/sponge/pkg/consulcli"
"github.com/zhufuyi/sponge/pkg/etcdcli"
"github.com/zhufuyi/sponge/pkg/grpc/grpccli"
"github.com/zhufuyi/sponge/pkg/nacoscli"
"github.com/zhufuyi/sponge/pkg/servicerd/registry"
"github.com/zhufuyi/sponge/pkg/servicerd/registry/consul"
"github.com/zhufuyi/sponge/pkg/servicerd/registry/etcd"
"github.com/zhufuyi/sponge/pkg/servicerd/registry/nacos"
"go.uber.org/zap"
"google.golang.org/grpc"
)
func initServerNameExampleClient() *grpc.ClientConn {
err := config.Init(configs.Path("serverNameExample.yml"))
if err != nil {
panic(err)
}
endpoint := fmt.Sprintf("127.0.0.1:%d", config.Get().Grpc.Port)
var cliOptions = []grpccli.Option{
grpccli.WithEnableLog(zap.NewNop()),
//grpccli.WithEnableLoadBalance(),
//grpccli.WithEnableRetry(),
}
if config.Get().App.RegistryDiscoveryType != "" {
var iDiscovery registry.Discovery
endpoint = "discovery:///" + config.Get().App.Name // Connecting to grpc services by service name
// Use consul service discovery, note that the host field in the configuration file serverNameExample.yml
// needs to be filled with the local ip, not 127.0.0.1, to do the health check
if config.Get().App.RegistryDiscoveryType == "consul" {
cli, err := consulcli.Init(config.Get().Consul.Addr, consulcli.WithWaitTime(time.Second*2))
if err != nil {
panic(err)
}
iDiscovery = consul.New(cli)
}
// Use etcd service discovery, use the command etcdctl get / --prefix to see if the service is registered before testing,
// note: the IDE using a proxy may cause the connection to the etcd service to fail
if config.Get().App.RegistryDiscoveryType == "etcd" {
cli, err := etcdcli.Init(config.Get().Etcd.Addrs, etcdcli.WithDialTimeout(time.Second*2))
if err != nil {
panic(err)
}
iDiscovery = etcd.New(cli)
}
// Use nacos service discovery
if config.Get().App.RegistryDiscoveryType == "nacos" {
// example: endpoint = "discovery:///serverName.scheme"
endpoint = "discovery:///" + config.Get().App.Name + ".grpc"
cli, err := nacoscli.NewNamingClient(
config.Get().NacosRd.IPAddr,
config.Get().NacosRd.Port,
config.Get().NacosRd.NamespaceID)
if err != nil {
panic(err)
}
iDiscovery = nacos.New(cli)
}
cliOptions = append(cliOptions, grpccli.WithDiscovery(iDiscovery))
}
if config.Get().App.EnableTracing {
cliOptions = append(cliOptions, grpccli.WithEnableTrace())
}
if config.Get().App.EnableCircuitBreaker {
cliOptions = append(cliOptions, grpccli.WithEnableCircuitBreaker())
}
if config.Get().App.EnableMetrics {
cliOptions = append(cliOptions, grpccli.WithEnableMetrics())
}
conn, err := grpccli.DialInsecure(context.Background(), endpoint, cliOptions...)
if err != nil {
panic(err)
}
return conn
}
{{- range .PbServices}}
func Test_{{.LowerName}}_methods(t *testing.T) {
conn := initServerNameExampleClient()
cli := serverNameExampleV1.New{{.Name}}Client(conn)
ctx, _ := context.WithTimeout(context.Background(), time.Second*3)
tests := []struct {
name string
fn func() (interface{}, error)
wantErr bool
}{
{{- range .Methods}}
{
name: "{{.MethodName}}",
fn: func() (interface{}, error) {
// todo enter parameters before testing
req := &serverNameExampleV1.{{.MethodName}}Request{
{{- range .RequestFields}}
{{.FieldName}}: {{.GoTypeZero}}, {{if .Comment}} {{.Comment}}{{end}}
{{- end}}
}
return cli.{{.MethodName}}(ctx, req)
},
wantErr: false,
},
{{- end}}
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.fn()
if (err != nil) != tt.wantErr {
t.Errorf("test '%s' error = %v, wantErr %v", tt.name, err, tt.wantErr)
return
}
t.Logf("reply data: %+v", got)
})
}
}
{{- end}}
`

View File

@@ -9,14 +9,38 @@ import (
// ServiceMethod method fields
type ServiceMethod struct {
MethodName string // Create
Request string // CreateRequest
Reply string // CreateReply
MethodName string // e.g. Create
Request string // e.g. CreateRequest
RequestFields []RequestField // request fields
Reply string // e.g. CreateReply
ServiceName string // Greeter
LowerServiceName string // greeter first character to lower
}
// RequestField request fields
type RequestField struct {
FieldName string
FieldType string
Comment string
}
// GoTypeZero default zero value for type
func (r RequestField) GoTypeZero() string {
switch r.FieldType {
case "bool":
return "false"
case "int32", "uint32", "sint32", "int64", "uint64", "sint64", "sfixed32", "fixed32", "sfixed64", "fixed64":
return "0"
case "float", "double":
return "0.0"
case "string":
return `""`
default:
return "nil"
}
}
// AddOne counter
func (t *ServiceMethod) AddOne(i int) int {
return i + 1
@@ -40,6 +64,7 @@ func parsePbService(s *protogen.Service) *PbService {
methods = append(methods, &ServiceMethod{
MethodName: m.GoName,
Request: m.Input.GoIdent.GoName,
RequestFields: getRequestFields(m.Input.Fields),
Reply: m.Output.GoIdent.GoName,
ServiceName: s.GoName,
@@ -54,6 +79,18 @@ func parsePbService(s *protogen.Service) *PbService {
}
}
func getRequestFields(fields []*protogen.Field) []RequestField {
var reqFields []RequestField
for _, field := range fields {
reqFields = append(reqFields, RequestField{
FieldName: field.GoName,
FieldType: field.Desc.Kind().String(),
Comment: strings.ReplaceAll(field.Comments.Trailing.String(), "\n", ""),
})
}
return reqFields
}
// GetServices parse protobuf services
func GetServices(file *protogen.File) []*PbService {
var pss []*PbService

View File

@@ -6,6 +6,7 @@ import (
"fmt"
"os"
"path/filepath"
"strings"
"time"
"github.com/zhufuyi/sponge/cmd/protoc-gen-go-rpc-tmpl/internal/generate/service"
@@ -70,7 +71,7 @@ func main() {
func saveRPCTmplFiles(gen *protogen.Plugin, f *protogen.File, moduleName string, serverName string, tmplOut string, ecodeOut string) error {
filenamePrefix := f.GeneratedFilenamePrefix
tmplFileContent, ecodeFileContent := service.GenerateFiles(f)
tmplFileContent, testTmplFileContent, ecodeFileContent := service.GenerateFiles(f)
filePath := filenamePrefix + ".go"
err := saveFile(moduleName, serverName, tmplOut, filePath, tmplFileContent, false)
@@ -78,6 +79,12 @@ func saveRPCTmplFiles(gen *protogen.Plugin, f *protogen.File, moduleName string,
return err
}
filePath = filenamePrefix + "_client_test.go"
err = saveFile(moduleName, serverName, tmplOut, filePath, testTmplFileContent, true)
if err != nil {
return err
}
filePath = filenamePrefix + "_rpc.go"
err = saveFileSimple(ecodeOut, filePath, ecodeFileContent, false)
if err != nil {
@@ -108,6 +115,7 @@ func saveFile(moduleName string, serverName string, out string, filePath string,
content = bytes.ReplaceAll(content, []byte("moduleNameExample"), []byte(moduleName))
content = bytes.ReplaceAll(content, []byte("serverNameExample"), []byte(serverName))
content = bytes.ReplaceAll(content, firstLetterToUpper("serverNameExample"), firstLetterToUpper(serverName))
return os.WriteFile(file, content, 0666)
}
@@ -133,3 +141,11 @@ func isExists(path string) bool {
}
return true
}
func firstLetterToUpper(s string) []byte {
if s == "" {
return []byte{}
}
return []byte(strings.ToUpper(s[:1]) + s[1:])
}

View File

@@ -169,6 +169,7 @@ func addRPCGwFields(moduleName string, serverName string, projectName string, re
{
Old: "serverNameExample",
New: serverName,
IsCaseSensitive: true,
},
// docker image and k8s deployment script replacement
{

View File

@@ -113,8 +113,8 @@ func Test_userExampleService_methods(t *testing.T) {
{
name: "Create",
fn: func() (interface{}, error) {
// todo test after filling in parameters
return cli.Create(ctx, &serverNameExampleV1.CreateUserExampleRequest{
// todo enter parameters before testing
req := &serverNameExampleV1.CreateUserExampleRequest{
Name: "foo7",
Email: "foo7@bar.com",
Password: "f447b20a7fcbf53a5d5be013ea0b15af",
@@ -122,7 +122,8 @@ func Test_userExampleService_methods(t *testing.T) {
Avatar: "http://internal.com/7.jpg",
Age: 11,
Gender: 2,
})
}
return cli.Create(ctx, req)
},
wantErr: false,
},
@@ -130,12 +131,13 @@ func Test_userExampleService_methods(t *testing.T) {
{
name: "UpdateByID",
fn: func() (interface{}, error) {
// todo test after filling in parameters
return cli.UpdateByID(ctx, &serverNameExampleV1.UpdateUserExampleByIDRequest{
// todo enter parameters before testing
req := &serverNameExampleV1.UpdateUserExampleByIDRequest{
Id: 7,
Phone: "16000000001",
Age: 11,
})
}
return cli.UpdateByID(ctx, req)
},
wantErr: false,
},
@@ -143,10 +145,11 @@ func Test_userExampleService_methods(t *testing.T) {
{
name: "DeleteByID",
fn: func() (interface{}, error) {
// todo test after filling in parameters
return cli.DeleteByID(ctx, &serverNameExampleV1.DeleteUserExampleByIDRequest{
// todo enter parameters before testing
req := &serverNameExampleV1.DeleteUserExampleByIDRequest{
Id: 100,
})
}
return cli.DeleteByID(ctx, req)
},
wantErr: false,
},
@@ -154,10 +157,11 @@ func Test_userExampleService_methods(t *testing.T) {
{
name: "GetByID",
fn: func() (interface{}, error) {
// todo test after filling in parameters
return cli.GetByID(ctx, &serverNameExampleV1.GetUserExampleByIDRequest{
// todo enter parameters before testing
req := &serverNameExampleV1.GetUserExampleByIDRequest{
Id: 1,
})
}
return cli.GetByID(ctx, req)
},
wantErr: false,
},
@@ -165,10 +169,11 @@ func Test_userExampleService_methods(t *testing.T) {
{
name: "ListByIDs",
fn: func() (interface{}, error) {
// todo test after filling in parameters
return cli.ListByIDs(ctx, &serverNameExampleV1.ListUserExampleByIDsRequest{
// todo enter parameters before testing
req := &serverNameExampleV1.ListUserExampleByIDsRequest{
Ids: []uint64{1, 2, 3},
})
}
return cli.ListByIDs(ctx, req)
},
wantErr: false,
},
@@ -176,8 +181,8 @@ func Test_userExampleService_methods(t *testing.T) {
{
name: "List",
fn: func() (interface{}, error) {
// todo test after filling in parameters
return cli.List(ctx, &serverNameExampleV1.ListUserExampleRequest{
// todo enter parameters before testing
Params: &types.Params{
Page: 0,
Limit: 10,
@@ -231,7 +236,7 @@ func Test_userExampleService_benchmark(t *testing.T) {
{
name: "GetByID",
fn: func() error {
// todo test after filling in parameters
// todo enter parameters before testing
message := &serverNameExampleV1.GetUserExampleByIDRequest{
Id: 1,
}
@@ -247,7 +252,7 @@ func Test_userExampleService_benchmark(t *testing.T) {
{
name: "ListByIDs",
fn: func() error {
// todo test after filling in parameters
// todo enter parameters before testing
message := &serverNameExampleV1.ListUserExampleByIDsRequest{
Ids: []uint64{1, 2, 3},
}
@@ -263,7 +268,7 @@ func Test_userExampleService_benchmark(t *testing.T) {
{
name: "List",
fn: func() error {
// todo test after filling in parameters
// todo enter parameters before testing
message := &serverNameExampleV1.ListUserExampleRequest{
Params: &types.Params{
Page: 0,

View File

@@ -13,7 +13,7 @@ import (
func UnaryServerRecovery() grpc.UnaryServerInterceptor {
// https://pkg.go.dev/github.com/grpc-ecosystem/go-grpc-middleware/recovery
customFunc := func(p interface{}) (err error) {
return status.Errorf(codes.Internal, "panic triggered: %v", p)
return status.Errorf(codes.Internal, "triggered panic: %v", p)
}
opts := []grpc_recovery.Option{
grpc_recovery.WithRecoveryHandler(customFunc),
@@ -26,7 +26,7 @@ func UnaryServerRecovery() grpc.UnaryServerInterceptor {
func StreamServerRecovery() grpc.StreamServerInterceptor {
// https://pkg.go.dev/github.com/grpc-ecosystem/go-grpc-middleware/recovery
customFunc := func(p interface{}) (err error) {
return status.Errorf(codes.Internal, "panic triggered: %v", p)
return status.Errorf(codes.Internal, "triggered panic: %v", p)
}
opts := []grpc_recovery.Option{
grpc_recovery.WithRecoveryHandler(customFunc),

View File

@@ -258,7 +258,7 @@ message List{{.TableName}}Reply {
{
name: "Create",
fn: func() (interface{}, error) {
// todo test after filling in parameters
// todo enter parameters before testing
// serviceCreateStructCode
},
wantErr: false,
@@ -267,7 +267,7 @@ message List{{.TableName}}Reply {
{
name: "UpdateByID",
fn: func() (interface{}, error) {
// todo test after filling in parameters
// todo enter parameters before testing
// serviceUpdateStructCode
},
wantErr: false,