发布于 2026-01-06 6 阅读
0

通过实例学习 Go:第 6 部分 - 使用 Go 创建 gRPC 应用

通过实例学习 Go:第 6 部分 - 使用 Go 创建 gRPC 应用

在之前的文章中,我们创建了一个HTTP REST API 服务器、一个CLI、一个Discord 机器人,甚至还有一个Nintendo Game Boy Advance 游戏。今天,让我们创建另一种类型的应用程序:一个用 Go 语言编写的 gRPC 应用程序!

gRPC

gRPC煎饼

首先,什么是gRPC?

gRPC是一个现代化的开源远程过程调用 (RPC) 框架,最初由 Google 开发。

gRPC 的核心思想是定义一个服务,并指定可以远程调用的方法及其参数和返回类型。在服务器端,服务器实现此接口并运行 gRPC 服务器来处理客户端调用。在客户端,客户端有一个存根(在某些语言中简称为客户端),它提供与服务器相同的方法。

gRPC模式

它使用Protocol Buffers,这是谷歌的开源技术,用于序列化和反序列化结构化数据。

gRPC 使用HTTP/2作为传输层(延迟更低、响应复用、服务器端流式传输、客户端流式传输甚至双向流式传输……)

每个 RPC 服务都在一个文件中声明protobuf

通过此.proto文件,您可以生成多种语言的客户端。

因此,gRPC 的强大之处在于它与语言无关:你可以用 Go 编写一个服务器,然后用 Java、Python、Rust、Go 等多种语言编写多个客户端……

如果您有需要相互通信的微服务,gRPC 可以替代 REST API 接口作为解决方案。

初始化

我们在上一篇文章中创建了Git 仓库,现在只需要将其检索到本地即可:



$ git clone https://github.com/scraly/learning-go-by-examples.git
$ cd learning-go-by-examples


Enter fullscreen mode Exit fullscreen mode

我们将为go-gopher-grpcCLI 应用程序创建一个文件夹,然后进入该文件夹:



$ mkdir go-gopher-grpc
$ cd go-gopher-grpc


Enter fullscreen mode Exit fullscreen mode

现在,我们需要初始化 Go 模块(依赖管理):



$ go mod init github.com/scraly/learning-go-by-examples/go-gopher-grpc
go: creating new go.mod: module github.com/scraly/learning-go-by-examples/go-gopher-grpc


Enter fullscreen mode Exit fullscreen mode

这将创建一个go.mod类似这样的文件:



module github.com/scraly/learning-go-by-examples/go-gopher-grpc

go 1.16


Enter fullscreen mode Exit fullscreen mode

在开始我们的超级 gRPC 应用程序之前,按照良好实践,我们将创建一个简单的代码组织结构。

创建以下文件夹结构:



.
├── README.md
├── bin
├── go.mod
└── test-results


Enter fullscreen mode Exit fullscreen mode

就这些?是的,我们代码组织结构的其余部分很快就会创建完成 ;-)。

创建我们的 CLI 应用程序

与第二篇文章类似,我们将创建一个命令行界面 (CLI) 应用程序。
如果您不了解Cobra,我建议您先阅读CLI 相关文章,然后再继续学习。

安装 Cobra:



$ go get -u github.com/spf13/cobra@latest


Enter fullscreen mode Exit fullscreen mode

生成我们的 CLI 应用程序结构和导入语句:



$ cobra init --pkg-name github.com/scraly/learning-go-by-examples/go-gopher-grpc
Your Cobra application is ready at
/Users/aurelievache/git/github.com/scraly/learning-go-by-examples/go-gopher-grpc


Enter fullscreen mode Exit fullscreen mode

我们的应用程序已初始化,已创建main.go文件和cmd/文件夹,代码结构如下:



.
├── LICENSE
├── bin
├── cmd
│   └── root.go
├── go.mod
├── go.sum
├── main.go
└── test-results


Enter fullscreen mode Exit fullscreen mode

与 CLI 文章中所述类似,我们需要使用Viperroot.go ,因此需要安装它:



$ go get github.com/spf13/viper@v1.8.1


Enter fullscreen mode Exit fullscreen mode

让我们创建 gRPC 客户端和服务端

我们需要一个 gRPC 应用程序,所以我们首先需要做的是创建一个应用程序server和一个client命令:



$ cobra add client
client created at /Users/aurelievache/git/github.com/scraly/learning-go-by-examples/go-gopher-grpc

$ cobra add server
server created at /Users/aurelievache/git/github.com/scraly/learning-go-by-examples/go-gopher-grpc


Enter fullscreen mode Exit fullscreen mode

现在,cmd/文件夹代码组织结构中应该包含以下文件:



cmd
├── client.go
├── root.go
└── server.go


Enter fullscreen mode Exit fullscreen mode

此时,该go.mod文件应包含以下导入语句:



module github.com/scraly/learning-go-by-examples/go-gopher-grpc

go 1.16

require (
    github.com/spf13/cast v1.4.0 // indirect
    github.com/spf13/cobra v1.2.1
    github.com/spf13/viper v1.8.1
    golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
    golang.org/x/text v0.3.6 // indirect
)


Enter fullscreen mode Exit fullscreen mode

为了向用户解释我们应用程序的目标和用途,我们需要编辑该root.go文件:



// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
    Use:   "go-gopher-grpc",
    Short: "gRPC app in Go",
    Long:  `gRPC application written in Go.`,
}


Enter fullscreen mode Exit fullscreen mode

是时候执行我们的应用程序了:



$ go run main.go
gRPC application written in Go.

Usage:
  go-gopher-grpc [command]

Available Commands:
  client      A brief description of your command
  completion  generate the autocompletion script for the specified shell
  help        Help about any command
  server      A brief description of your command

Flags:
      --config string   config file (default is $HOME/.go-gopher-grpc.yaml)
  -h, --help            help for go-gopher-grpc
  -t, --toggle          Help message for toggle

Use "go-gopher-grpc [command] --help" for more information about a command.


Enter fullscreen mode Exit fullscreen mode

默认情况下会显示使用说明,完美!

让我们测试一下我们的client命令server



$ go run main.go client
client called

$ go run main.go server
server called


Enter fullscreen mode Exit fullscreen mode

好的,clientandserver命令也得到了响应。

让我们创建我们的原型

正如我们所说,默认情况下,gRPC 使用Protocol Buffers

JSON

使用 Protocol Buffers 的第一步是定义要序列化到.proto文件中的数据结构。

让我们gopher.proto在新文件夹下创建一个文件pkg/gopher/



syntax = "proto3";
package gopher;

option go_package = "github.com/scraly/learning-by-examples/go-gopher-grpc";

// The gopher service definition.
service Gopher {
  // Get Gopher URL
  rpc GetGopher (GopherRequest) returns (GopherReply) {}
}

// The request message containing the user's name.
message GopherRequest {
  string name = 1;
}

// The response message containing the greetings
message GopherReply {
  string message = 1;
}


Enter fullscreen mode Exit fullscreen mode

.proto

让我们来解释一下。
这个.proto文件公开了我们的Gopher服务,该服务有一个GetGopher函数,任何用任何语言编写的 gRPC 客户端都可以调用该函数。

gRPC 受多种编程语言支持,因此需要与 gRPC 服务器交互的微服务可以使用.proto输出文件中的代码生成自己的代码。

option go_package要生成 Go 代码,必须提供 Go 包的导入路径,每个.proto文件都必须提供该路径。

从 proto 生成 Go 代码

现在,我们需要安装Protocol Buffers v3

适用于 macOS:



$ brew install protoc


Enter fullscreen mode Exit fullscreen mode

检查 protoc 是否已正确安装:



$ protoc --version
libprotoc 3.17.3


Enter fullscreen mode Exit fullscreen mode

现在我们需要借助protoc工具生成 Go gRPC 代码:



$ protoc --go_out=plugins=grpc:. --go_opt=paths=source_relative pkg/gopher/gopher.proto


Enter fullscreen mode Exit fullscreen mode

文件夹中应该会多出一个新文件pkg/gopher



pkg/gopher
├── gopher.pb.go
└── gopher.proto


Enter fullscreen mode Exit fullscreen mode

gopher.go该文件包含生成的代码,我们将把该代码导入到我们的server.go文件中,以便将我们的 gRPC 服务器注册到Gopher服务。

让我们创建 gRPC 服务器

蓝地鼠队

现在是时候创建我们的 gRPC 服务器了,为此我们需要编辑我们的server.go文件。

首先,我们初始化名为 cmd 的包,以及我们需要导入的所有依赖项/库:



package cmd

import (
    "context"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "log"
    "net"
    "net/http"
    "strings"

    "github.com/spf13/cobra"
    "golang.org/x/xerrors"

    pb "github.com/scraly/learning-go-by-examples/go-gopher-grpc/pkg/gopher"
    "google.golang.org/grpc"
)


Enter fullscreen mode Exit fullscreen mode

然后,我们初始化常量:



const (
    port         = ":9000"
    KuteGoAPIURL = "https://kutego-api-xxxxx-ew.a.run.app"
)


Enter fullscreen mode Exit fullscreen mode

我们定义了两个结构体,一个用于服务器,一个用于 Gopher 数据。



// server is used to implement gopher.GopherServer.
type Server struct {
    pb.UnimplementedGopherServer
}

type Gopher struct {
    URL string `json: "url"`
}


Enter fullscreen mode Exit fullscreen mode

我们改进了 serverCmd run 函数,该函数用于初始化 gRPC 服务器、注册 RPC 服务并启动服务器:



// serverCmd represents the server command
var serverCmd = &cobra.Command{
    Use:   "server",
    Short: "Starts the Schema gRPC server",

    Run: func(cmd *cobra.Command, args []string) {
        lis, err := net.Listen("tcp", port)
        if err != nil {
            log.Fatalf("failed to listen: %v", err)
        }

        grpcServer := grpc.NewServer()

        // Register services
        pb.RegisterGopherServer(grpcServer, &Server{})

        log.Printf("GRPC server listening on %v", lis.Addr())

        if err := grpcServer.Serve(lis); err != nil {
            log.Fatalf("failed to serve: %v", err)
        }
    },
}


Enter fullscreen mode Exit fullscreen mode

最后,我们实现了GetGopher该方法。

等等,我们想要什么?
哎呀,不好意思,我忘了解释一下我们的服务器会提供什么服务了^^。

我们的 gRPC 应该实现一个 GetGopher 方法,该方法将:

  • 检查请求是否不为空,并且包含一个不为空的 Gopher 名称。
  • KuteGo API请求有关 Gopher 的信息
  • 返回 Gopher 的 URL


// GetGopher implements gopher.GopherServer
func (s *Server) GetGopher(ctx context.Context, req *pb.GopherRequest) (*pb.GopherReply, error) {
        res := &pb.GopherReply{}

    // Check request
    if req == nil {
        fmt.Println("request must not be nil")
        return res, xerrors.Errorf("request must not be nil")
    }

    if req.Name == "" {
        fmt.Println("name must not be empty in the request")
        return res, xerrors.Errorf("name must not be empty in the request")
    }

    log.Printf("Received: %v", req.GetName())

    //Call KuteGo API in order to get Gopher's URL
    response, err := http.Get(KuteGoAPIURL + "/gophers?name=" + req.GetName())
    if err != nil {
        log.Fatalf("failed to call KuteGoAPI: %v", err)
    }
    defer response.Body.Close()

    if response.StatusCode == 200 {
        // Transform our response to a []byte
        body, err := ioutil.ReadAll(response.Body)
        if err != nil {
            log.Fatalf("failed to read response body: %v", err)
        }

        // Put only needed informations of the JSON document in our array of Gopher
        var data []Gopher
        err = json.Unmarshal(body, &data)
        if err != nil {
            log.Fatalf("failed to unmarshal JSON: %v", err)
        }

        // Create a string with all of the Gopher's name and a blank line as separator
        var gophers strings.Builder
        for _, gopher := range data {
            gophers.WriteString(gopher.URL + "\n")
        }

        res.Message = gophers.String()
    } else {
        log.Fatal("Can't get the Gopher :-(")
    }

    return res, nil
}


Enter fullscreen mode Exit fullscreen mode

不要忘记原有的原始init功能:



func init() {
    rootCmd.AddCommand(serverCmd)
}


Enter fullscreen mode Exit fullscreen mode

安装我们的依赖项

和往常一样,如果您使用外部依赖项,则需要安装它们:



$ go get google.golang.org/grpc
$ go get golang.org/x/xerrors


Enter fullscreen mode Exit fullscreen mode

让我们创建 gRPC 客户端

蓝地鼠队

现在,我们可以创建 gRPC 客户端了,为此我们需要编辑我们的client.go文件。

我们初始化名为 cmd 的包,以及所有需要导入的依赖项/库:



package cmd

import (
    "context"
    "log"
    "os"
    "time"

    "google.golang.org/grpc"

    pb "github.com/scraly/learning-go-by-examples/go-gopher-grpc/pkg/gopher"
    "github.com/spf13/cobra"
)


Enter fullscreen mode Exit fullscreen mode

定义我们的常量:



const (
    address     = "localhost:9000"
    defaultName = "dr-who"
)


Enter fullscreen mode Exit fullscreen mode

我们改进了 clientCmd 运行函数,使其:

  • 初始化 gRPC 客户端
  • 连接到 gRPC 服务器
  • 调用 GetGopher 函数并传入 Gopher 的名称。
  • 返回“URL:” + gRPC 调用返回的消息


// clientCmd represents the client command
var clientCmd = &cobra.Command{
    Use:   "client",
    Short: "Query the gRPC server",

    Run: func(cmd *cobra.Command, args []string) {
        var conn *grpc.ClientConn
        conn, err := grpc.Dial(address, grpc.WithInsecure())
        if err != nil {
            log.Fatalf("did not connect: %s", err)
        }
        defer conn.Close()

        client := pb.NewGopherClient(conn)

        var name string

        // Contact the server and print out its response.
        // name := defaultName
        if len(os.Args) > 2 {
            name = os.Args[2]
        }
        ctx, cancel := context.WithTimeout(context.Background(), time.Second)
        defer cancel()
        r, err := client.GetGopher(ctx, &pb.GopherRequest{Name: name})
        if err != nil {
            log.Fatalf("could not greet: %v", err)
        }
        log.Printf("URL: %s", r.GetMessage())
    },
}


Enter fullscreen mode Exit fullscreen mode

也别忘了还有现有的init方法:



func init() {
    rootCmd.AddCommand(clientCmd)
}


Enter fullscreen mode Exit fullscreen mode

试试看!

让我们启动 gRPC 服务器:



$ go run main.go server
2021/08/07 14:57:27 GRPC server listening on [::]:9000


Enter fullscreen mode Exit fullscreen mode

然后,在终端的另一个标签页中,启动调用我们GetGopher方法并传入参数“gandalf”的gRPC客户端:



$ go run main.go client gandalf
2021/08/07 14:57:35 URL: https://raw.githubusercontent.com/scraly/gophers/main/gandalf.png


Enter fullscreen mode Exit fullscreen mode

我们的应用程序运行正常,它会返回“URL:”+所需Gopher的URL。

建好了!

您的应用程序已准备就绪,现在只需构建即可。
为此,我们将像之前的文章一样,使用Taskfile来自动化常见任务。

所以,对于这个应用,我也创建了一个Taskfile.yml包含以下内容的文件:



version: "3"

tasks:
    build:
        desc: Build the app
        cmds:
        - GOFLAGS=-mod=mod go build -o bin/gopher-grpc main.go 

    run: 
        desc: Run the app
        cmds:
        - GOFLAGS=-mod=mod go run main.go

    generate:
        desc: Generate Go code from protobuf
        cmds:
        - protoc --go_out=plugins=grpc:. --go_opt=paths=source_relative pkg/gopher/gopher.proto

    test:
        desc: Execute Unit Tests
        cmds:
        - gotestsum --junitfile test-results/unit-tests.xml -- -short -race -cover -coverprofile test-results/cover.out ./...


Enter fullscreen mode Exit fullscreen mode

正因如此,我们才能轻松构建我们的应用程序:



$ task build
task: [build] GOFLAGS=-mod=mod go build -o bin/gopher-grpc main.go


Enter fullscreen mode Exit fullscreen mode

让我们用新生成的可执行二进制文件再测试一次:



$ ./bin/gopher-grpc server
2021/08/07 15:07:20 GRPC server listening on [::]:9000


Enter fullscreen mode Exit fullscreen mode

在终端的另一个标签页中:



$ ./bin/gopher-grpc client yoda-gopher
2021/08/07 15:07:34 URL: https://raw.githubusercontent.com/scraly/gophers/main/yoda-gopher.png


Enter fullscreen mode Exit fullscreen mode

酷!这是我们可爱的尤达地鼠的网址!:-)

单元测试?

现在,我可以将我的 gRPC 服务器/微服务部署到生产环境中了,太棒了,谢谢,再见!

呃……等等,在那之前,正如你所知,测试我们的应用程序非常重要,这样才能确保应用在部署前能够按预期运行。
单元测试是一种强大的实践,在 Go 语言中,你甚至可以为 gRPC 应用创建单元测试。

使用 Golang,你不需要像 Java 中的 JUnit 那样导入外部包。它已经集成到核心包中,只需使用命令即可go test

让我们执行单元测试:



$ go test
?       github.com/scraly/learning-go-by-examples/go-gopher-grpc    [no test files]


Enter fullscreen mode Exit fullscreen mode

如您所见,0 个单元测试成功运行,正常^^
我们将在下一节中处理它们,但在此之前,我们将发现一个有用的工具gotestsum

戈特苏姆

Gotestsum,这是什么新工具?Go test 还不够吗?

让我们来回答这个问题。Go语言的优势之一在于它拥有丰富的工具生态系统,可以让我们的生活更加轻松便捷。

正如我们所见,该测试工具已与 Go 集成。这很方便,但例如,它对用户不太友好,也难以集成到所有 CI/CD 解决方案中。

这就是为什么gotestsum这个小巧的 Go 工具能够go test改进测试结果的显示,生成更易于阅读、更实用的报告,并可以直接输出 JUnit 格式的结果。这也是本文推荐的优秀实践之一 ;-)。

安装它:



$ go get gotest.tools/gotestsum


Enter fullscreen mode Exit fullscreen mode

让我们执行task test使用该工具的命令gotestsum



$ task test
task: [test] gotestsum --junitfile test-results/unit-tests.xml -- -short -race -cover -coverprofile test-results/cover.out ./...
∅  . (3ms)
∅  cmd
∅  pkg/gopher

DONE 0 tests in 1.409s


Enter fullscreen mode Exit fullscreen mode

上面的代码表明我们使用 gotestsum 工具运行单元测试,并将测试结果以JUnit格式导出到名为test-results/unit-tests.xml.

以下是一个 JUnit 格式的测试结果文件示例:



<?xml version="1.0" encoding="UTF-8"?>
<testsuites>
    <testsuite tests="0" failures="0" time="0.000000" name="github.com/scraly/learning-go-by-examples/go-gopher-grpc" timestamp="2021-08-11T14:23:36+02:00">
        <properties>
            <property name="go.version" value="go1.16.5 darwin/amd64"></property>
        </properties>
    </testsuite>
    <testsuite tests="0" failures="0" time="0.000000" name="github.com/scraly/learning-go-by-examples/go-gopher-grpc/cmd" timestamp="2021-08-11T14:23:36+02:00">
        <properties>
            <property name="go.version" value="go1.16.5 darwin/amd64"></property>
        </properties>
    </testsuite>
    <testsuite tests="0" failures="0" time="0.000000" name="github.com/scraly/learning-go-by-examples/go-gopher-grpc/pkg/gopher" timestamp="2021-08-11T14:23:36+02:00">
        <properties>
            <property name="go.version" value="go1.16.5 darwin/amd64"></property>
        </properties>
    </testsuite>
</testsuites>


Enter fullscreen mode Exit fullscreen mode

如何测试gRPC?

我们的应用是一个 gRPC 客户端/服务器架构,这意味着当我们调用该getGopher方法时,会触发客户端/服务器通信,但我们的单元测试中无需测试 gRPC 调用本身。我们只会测试应用的智能性。

正如我们所看到的,我们的 gRPC 服务器基于一个名为pkg/gopher/gopher.proto.

标准 Go 库提供了一个包,允许我们测试 Go 程序。Go 测试文件必须与要测试的文件放在同一文件夹中,并且以 .test 为_test.go扩展名。必须遵循此规则,以便 Go 可执行文件能够识别我们的测试文件。

第一步是创建一个server_test.go文件,并将其放置在旁边server.go

我们将为这个测试文件命名包cmd_test,首先导入测试包,然后创建我们要测试的函数,如下所示:



package cmd_test

import "testing"

func TestGetGopher(t *testing.T) {

}


Enter fullscreen mode Exit fullscreen mode

/!\警告:每个测试函数必须写成funcTest***(t *testing.T),其中***表示我们要测试的函数的名称。

让我们用表格驱动测试来编写测试

在我们的应用程序中,我们不会测试所有内容,而是从测试业务逻辑和应用程序的智能部分开始。在我们的应用程序中,我们关注的是其内部结构server.go,特别是以下GetGopher功能:



func (s *server) GetGopher(ctx context.Context, req *pb.GopherRequest) (*pb.GopherReply, error) {
    res := &pb.GopherReply{}
...


Enter fullscreen mode Exit fullscreen mode

如您所见,为了尽可能覆盖我们的代码,我们至少需要测试三种情况:

  • 请求为空。
  • 请求为空(名称字段为空)。
  • 请求中已填写姓名栏。

表格驱动测试

我们将采用表格驱动测试的方法,而不是创建测试用例方法并复制粘贴,这将使工作变得容易得多。

编写好的测试并非易事,但在很多情况下,表格驱动测试可以覆盖很多内容:表格中的每个条目都是一个完整的测试用例,包含输入和预期结果。有时还会提供一些额外信息。测试输出易于阅读。如果您在编写测试时经常使用复制粘贴,不妨问问自己,重构为表格驱动测试是否是更好的选择。

给定一个测试用例表,实际测试只需扫描表中的所有条目,并对每个条目执行必要的测试。测试代码只需编写一次,之后便可对所有表条目重复执行。因此,编写包含清晰错误信息的全面测试变得更加容易。

首先,安装所需的外部依赖项:



$ go get github.com/onsi/gomega


Enter fullscreen mode Exit fullscreen mode

让我们来定义一下我们的软件包及其依赖项:



package cmd_test

import (
    "context"
    "testing"

    cmd "github.com/scraly/learning-go-by-examples/go-gopher-grpc/cmd"
    pb "github.com/scraly/learning-go-by-examples/go-gopher-grpc/pkg/gopher"

    . "github.com/onsi/gomega"
)


Enter fullscreen mode Exit fullscreen mode

然后,我们在函数中定义测试用例TestGetGopher



func TestGetGopher(t *testing.T) {
    s := cmd.Server{}

    testCases := []struct {
        name        string
        req         *pb.GopherRequest
        message     string
        expectedErr bool
    }{
        {
            name:        "req ok",
            req:         &pb.GopherRequest{Name: "yoda-gopher"},
            message:     "https://raw.githubusercontent.com/scraly/gophers/main/yoda-gopher.png\n",
            expectedErr: false,
        },
        {
            name:        "req with empty name",
            req:         &pb.GopherRequest{},
            expectedErr: true,
        },
        {
            name:        "nil request",
            req:         nil,
            expectedErr: true,
        },
    }


Enter fullscreen mode Exit fullscreen mode

良好的做法是为测试用例命名,这样如果在执行过程中发生错误,就会显示测试用例的名称,方便我们查看错误所在。

然后,我遍历所有测试用例。我调用我的服务,并根据是否等待错误发生来测试它是否存在,否则测试结果是否符合预期:



    for _, tc := range testCases {
        testCase := tc
        t.Run(testCase.name, func(t *testing.T) {
            t.Parallel()
            g := NewGomegaWithT(t)

            ctx := context.Background()

            // call
            response, err := s.GetGopher(ctx, testCase.req)

            t.Log("Got : ", response)

            // assert results expectations
            if testCase.expectedErr {
                g.Expect(response).ToNot(BeNil(), "Result should be nil")
                g.Expect(err).ToNot(BeNil(), "Result should be nil")
            } else {
                g.Expect(response.Message).To(Equal(testCase.message))
            }
        })
    }
}


Enter fullscreen mode Exit fullscreen mode

Aurélie,你的代码很棒!但是,为什么不直接使用 `i` 呢testCase,而要创建一个新的变量 `a` 并让它接收一个值 `b` 呢tctc

简而言之,如果没有这一行代码,就会出现t.Parallel()Go 语言中一个众所周知的 bug——我们使用了一个位于 go 例程中的闭包。因此,原本应该执行三个测试用例:“req ok”、“req with empty name” 和 “nil request”,现在却会运行三个测试用例,但每次运行的值都与第一个测试用例相同 :-(。

那么,Gomega是什么?

戈梅加

Gomega是一个 Go 语言库,它允许你进行断言。在我们的示例中,我们检查获取到的值是 null、非 null 还是等于某个特定值,但 Gomega 库的功能远不止这些。

让我们运行单元测试吧!

要运行新创建的单元测试,如果您使用Visual Studio Code,可以直接在 IDE 中运行它们;非常方便:

首先,打开server_test.go文件。

然后,点击“运行包测试”链接:
运行包测试

现在,打开server.go文件:
server.go

绿色高亮部分是测试覆盖的代码——太棒了!红色线条部分是单元测试未覆盖的代码 ;-)。

否则,我们可以借助我们出色的 Taskfile,在命令行中运行项目的所有单元测试:



$ task test
task: [test] gotestsum --junitfile test-results/unit-tests.xml -- -short -race -cover -coverprofile test-results/cover.out ./...
∅  . (1ms)
✓  cmd (1.388s) (coverage: 41.5% of statements)
∅  pkg/gopher

DONE 4 tests in 7.787s


Enter fullscreen mode Exit fullscreen mode

太好了,这是单元测试之旅的开始 :-)。

如果你习惯于在编写测试用例时复制粘贴,我建议你认真了解一下表格驱动测试 :-)。这确实是一种非常好的单元测试实践,正如我们所见,编写覆盖代码的单元测试变得轻而易举。

结论

正如你在本文和之前的文章中看到的,使用 Go 可以创建多个不同的应用程序……并且可以编写单元测试,而无需从 StackOverflow 复制粘贴代码 ;-)。

我们用 Go 语言编写的 gRPC 应用的所有代码都可以在这里找到:https://github.com/scraly/learning-go-by-examples/tree/main/go-gopher-grpc

在接下来的文章中,我们将用 Go 创建其他类型的应用程序。

希望你会喜欢。

文章来源:https://dev.to/aurelievache/learning-go-by-examples-part-6-create-a-grpc-app-in-go-2ja3