探索gRPC测试:使用Postman、C#和Python客户端的实际示例

2565 篇文章 2 订阅
2402 篇文章 14 订阅

在这里插入图片描述

以下为作者观点:

几个月前,我职业生涯中第一次接触到 gRPC 服务。在此之前,我一直在使用 SOAP,主要是使用 REST 和 GraphQL API,只听说过 gRPC。实际上,它并不难,你可以轻松地使用它。经过一段时间的尝试和使用后,我决定创建一种 gRPC 测试入门指南。

在本文中,我们将尝试了解 gRPC 的一般概念以及如何使用 gRPC 协议创建测试。为了提供广泛的示例,我决定使用以下客户端创建测试:

  • Postman——对于那些不熟悉代码的人来说很容易理解

  • C# — 我最喜欢的编程语言。适合喜欢 Java 或 C# 的老派人士。是的。对我来说入门比较容易。

  • Python — 当今非常流行的脚本编程语言。

也可能还有其他编程语言,但我决定就此打住。不再介绍。让我们跳到 gRPC 世界……

什么是 gRPC?

根据Wikipedia 的介绍,gRPC 是一个高性能、开源的远程过程调用(RPC),最初由 Google 设计。

编者注,根据百度百科介绍:gRPC是一个现代的开源高性能远程过程调用(RPC)框架,可以在任何环境中运行。它可以高效地连接数据中心内和跨数据中心的服务,支持负载平衡、跟踪、运行状况检查和身份验证。它也适用于分布式计算的最后一英里,将设备、移动应用程序和浏览器连接到后端服务。gRPC最初是由Google创建的,它使用了一个通用的RPC基础设施称为Stubby,用于连接大量微服务在其数据中心内部和之间运行了十多年。2015年3月, Google决定构建Stubby的下一个版本,并将其开源。gRPC现在在许多组织中使用, 谷歌将为从微服务到计算的“最后一英里”的用例提供动力 (移动的、Web和物联网)。

简单来说,它是关于调用远程服务器上的函数/方法以执行某些操作。让我们考虑一个具体的例子:

你想创建一个新用户。

在服务器上我们实现了可调用的特定函数/方法。

我们使用客户端向服务器发送请求并接收响应(成功或不成功)。

现在,我感觉我正在解释客户端-服务器架构的基础知识:),我们的想法实际上是调用服务器上实现的函数。

图片

沟通实际上是如何发生的?

默认情况下,GRPC 使用协议缓冲区。我将其称为契约。这是什么意思?这意味着,服务器和任何客户端都将使用相同的模式进行通信。很酷的是,协议缓冲区是一种与语言无关、与平台无关的可扩展机制,用于序列化结构化数据。

- 该模式应在以“.proto”为扩展名的文件文本内定义。

- 此文件的第一个构建块是“消息”。将其视为具有我们想要发送或接收的字段的模型。字段结构是

a) 数据类型。预期发送或接收的数据类型。例如,它可能是字符串、布尔值、数字。请阅读有关数据类型的更多信息。

b) 字段名称。你计划发送的字段的名称

c) 订单号。如 1、2、3、…n

图片

- 第二个基本上是我们的服务与 rpc 函数签名。这些函数内部使用消息作为输入和输出参数,grpc 服务希望我们发送消息,我们期望它返回响应给我们

图片

然后,一旦你指定了数据结构,就可以使用协议缓冲区编译器protoc从原型定义生成首选语言的数据访问类。一旦执行请求,它就会自动将其转换为字节并将其发送到服务器。服务器根据实施的逻辑返回响应。从测试的角度来看,可以使用不同的参数调用方法,服务器应该根据你的要求和实施的逻辑做出反应。理论已经足够了:)让我们开始实践吧。

测试服务器

为了简单起见,我提供了一个模拟用户管理系统的简单服务器。听起来很不可思议。它只包含三种方法:)

CreateUser — 创建用户并返回它

GetUsers — 获取所有用户

GetUsersAsArray — 获取所有用户并将其以数组形式返回。

我们想要做的是在开始创建测试之前运行此服务器

先决条件

1.下载GitHub 项目

2.按照自述文件说明运行服务器。步骤很简单

图片

Proto 文件

Proto 文件可以在 GrpcServer →Protos 文件夹中找到

图片

你将在其他客户端中使用此文件。因此,你可能需要复制它或记住它的位置

图片

完成服务器后,我们就可以开始测试了。首先使用 Postman 进行简单的测试

使用 Postman 进行 GRPC 测试

最后我们来到了最有趣的部分。我们将发送请求并创建测试。至少一个:)

测试创建

  • 在 Postman 中,我们可以发起一个新的请求并选择“gRPC”。就这么简单。

图片

  • 接下来,我们要指定服务器 URL 并导入 .proto 文件。

注意:还有其他与 .proto 文件通信的选项。但在此阶段,我们想简化它。

图片

  • 一旦文件导入,可能已经可以在“选择方法”部分看到可用的请求

图片

  • 让我们尝试创建一个用户创建请求。选择方法后,我们需要准备一条消息。Postman 已经包含一个非常有用的按钮用于示例生成 — “使用示例消息”。如果不喜欢某些值,可以更改这些值。

图片

注意:本文中我们只使用“CreateUser”方法,但可以尝试其他方法。它也应该有效。

  • 最后我们可以使用invoke,新创建的用户将会返回给我们。

图片

  • 让我们从请求中进行一个简单的测试。让我们转到脚本并使用代码片段进行状态代码验证。在现实生活中,这还不够,但我们正在考虑概念

图片

  • 一旦再次运行测试,将看到结果。

下一步

在上面的例子中,我们仅考虑了简单的场景和非常原始的验证步骤。至于接下来的步骤,你可以考虑以下内容:

  • 添加更多断言步骤。仅状态码验证是不够的

  • 添加其他案例。我们仅考虑了一种积极情景。使用测试设计技术添加更多情景

  • 尝试从 CLI 运行它以运行它们继续集成。使用 Postman CLI 或 Newman(此步骤与我们的主题无关,但记住它总是好的)

接下来,我们将尝试使用 C# 来实现自动化。请稍候...

图片

使用 C# 客户端进行 GRPC 测试

我们将把它分为项目准备和测试设计部分。

项目准备

1.首先,我们将创建一个新的 C# 项目。我使用的是 NET8 目标框架和控制台应用程序。可以使用任何 .CORE/.NET 版本。

图片

2. 在我们的例子中,客户端和测试位于同一个项目中。因此,我们将所有 NuGet 包安装到同一个项目中。为简单起见,我们将示例分为 GRPC 相关包和测试相关包,以防想要分离项目。

图片

GRPC 客户端相关的包有:

“Google.Protobuf”——提供在 gRPC 客户端和服务器之间发送的消息编码和解码所需的 Protobuf 序列化和反序列化功能。

“Grpc.Net.Client” — 此包提供在 .NET 中创建 gRPC 客户端所需的组件。应用程序能够使用 HTTP/2 与 gRPC 服务进行通信。

“Grpc.Tools”——在构建时用于从.proto文件生成 C# 类,定义 gRPC 应用程序中使用的服务契约和消息类型。

测试相关的包有:

“Microsoft.NET.Test.Sdk”——在 .NET 项目中运行测试所需,它与 Visual Studio 和其他 CI/CD 系统集成以促进自动化测试工作流程

“NUnit”——充当允许编写和组织测试的测试框架。它提供断言条件和验证代码正确性的必要工具。

“NUnit3TestAdapter” — 将 NUnit 框架与 Visual Studio 连接起来,允许在 IDE 中无缝发现、执行测试并报告结果。它确保可以直接从 Visual Studio 运行 NUnit 测试。

本项目中安装的附加软件包(可选):

“FluentAssertions”——FluentAssertions 是一个库,它提供了流畅的语法,用于以 TDD/BDD 风格编写断言测试。

“Bogus”——Bogus 是一个用于生成用于测试和原型设计的虚假数据的库。它允许快速轻松地创建真实的测试数据,涵盖各种数据类型和模式。

3. 接下来,我们需要将.proto文件从服务器复制到客户端。

  • 创建一个Protos文件夹并添加.proto 文件。

  • 验证文件包含以下配置

构建操作 = Protobuf 编译器

gRPC 存根类 = 仅限客户端

图片

  • 验证项目属性也设置为客户端

图片

注意:在我们的示例中,我们直接复制了一个.proto文件。这不是唯一一种方式。实际上,可以.proto从某些存储(例如创建 NuGet 包)引用文件。在这种情况下,如果有新版本可用,可以避免文件实现问题。

4. 是时候构建你的项目了。完成后,你可能会发现自动生成的类。我们已准备好创建测试

图片

测试设计

1.首先我们需要建立一个频道和客户端。

[TestFixture]
public class CreateUsersTests
{
    private GrpcChannel? _channel;
    private User.UserClient? _userClient;

    [SetUp]
    public void SetUp()
    {
        // Setup new channel and client for each test
        _channel = GrpcChannel.ForAddress(TestContext.Parameters["server"]!);
        _userClient = new User.UserClient(_channel);
    }

    [TearDown]
    public void TearDown()
    {
        // Dispose the channel after each test
        _channel?.Dispose();
    }

- 我们将使用 NUnit [SetUp] 属性在方法内部执行此操作。这将使我们可以为每个测试单独设置,从而使我们的测试独立

- 如你所见,我们正在使用 GRPC NET 客户端的方法创建通道 -“GrpcChannel.ForAddress(<pass server url> here)”

- 客户端创建来自 GRPC.Protos。在我们的例子中,它是基于.proto文件生成的。示例:users.proto = User.UserClient(_channel);

- 最后我们要关闭一个通道来清理

2.接下来我们实现测试逻辑

[Test]
public async Task CreateUser_WithFullValidData_ShouldBeCreated()
{
    //Arrange
    UserRequestModel requestModel = new UserFaker().Generate();

    //Act
    UserResponseModel? response = await _userClient!.CreateUserAsync(requestModel).ResponseAsync;

    //Assert
    response.Should().NotBeNull();
    using (new AssertionScope())
    {
        response.Email.Should().Be(requestModel.Email);
        response.FirstName.Should().Be(requestModel.FirstName);
        response.LastName.Should().Be(requestModel.LastName);
        response.Age.Should().Be(requestModel.Age);
        response.IsDiscount.Should().Be(requestModel.IsDiscount);
        response.Id.Should().BeGreaterThan(0);
    }
}

安排部分包含请求的准备。我使用 Bogus 库用假数据创建它。但为了简单起见,模型可能看起来像这样

var requestModel = new UserRequestModel
{
    Email = "test@test.com",
    FirstName = "FirstName",
    LastName = "LastName",
    //.....Some other properties
};

该模型是基于 proto 消息契约生成的

message UserRequestModel {
 string email = 1; //Required
 string firstName = 2; //Required
 string lastName = 3; //Required
 google.protobuf.Int32Value age = 4; //Optional
 bool isDiscount = 5; //Required
 UserType userType = 6; //Required
}

下一步,我们将调用“CreateUserAsync”函数,该函数也是根据我们提供的合同为你生成的。一个区别是最后添加了“Async”后缀,以表明它是异步函数/方法。

service User {
 rpc CreateUser(UserRequestModel) returns (UserResponseModel);
 rpc GetUsers(Empty) returns (stream UserResponseModel);
 rpc GetUsersAsArray(google.protobuf.Empty) returns (UserListResponse);
}

注意:还有另一个同步方法可用 - “CreateUser”

最后一步是对我们收到的结果进行断言。查看代码示例时,我才意识到我忘了验证状态。但你可以轻松地做到这一点,但获取状态而不是 .ResponseAsync。

3. 测试已准备就绪。可以继续测试其他场景。

可能出现的负面情况

上述方法很容易适用于正面情况。不幸的是,如果尝试执行某些负面情况,GRPC 调用将发生异常并导致测试失败。那么在这种情况下该怎么办?

有几种方法可以解决此问题:

可以使用特定的断言来验证调用是否引发异常

在下面的例子中,我有一个验证年龄边界值的负面案例

var ex = Assert.ThrowsAsync<RpcException>(async () => await _userClient!.CreateUserAsync(requestModel).ResponseAsync);

断言.That(例如!.Status.StatusCode,Is.EqualTo(StatusCode.InvalidArgument));

Assert.That(ex.Status.Detail, Does.Contain(“年龄必须在 18 至 60 岁之间”));

图片

第二种方法需要包装你的代码以便拦截“RpcException”并返回它

这是一个更复杂的方法。无论如何,让我们尝试创建一些简单的实现

首先,让我们创建一个存储结果的模型。

图片

其次,我们需要创建一个函数/方法来捕获异常并返回它

  • 简单来说,需要在 try-catch 块内进行调用并首先捕获“RpcException”。

  • 无论是成功还是失败,都会返回一个带有状态代码的对象。成功的情况只会返回更多信息

图片

最后,我们可以使用此方法来执行我们的调用

  • 准备请求模型

  • 使用准备好的方法并从内部调用请求

  • 接收结果并执行断言

图片

注意:此代码肯定需要重构。我建议使用“GrpcRequestBase”类作为服务包装器的基类,例如“UserServiceRequests”或类似的东西。

下一步我们将使用 Python 创建一个测试项目。

使用 python 客户端进行 GRPC 测试

与以前一样,我们将把它分为项目准备和测试设计部分。

项目准备

我将使用 Pipenv。因此,项目设置将如下所示

1.如果尚未安装 Python,请先安装

2.安装 pipenv

pip install --upgrade pip
pip install pipenv

3.创建项目目录

mkdir my_grpc_project
cd my_grpc_project
4. 使用已安装的 Python 版本初始化 Pipenv 环境
pipenv --  python 3.8.2
5.激活Pipenv shell
pipenv shell

6.安装GRPC相关包(grpc客户端)

pipenv install grpcio grpcio-tools
7.安装测试相关包
pipenv install pytest pytest-asyncio --dev

8. 应创建 Pip 文件。它应类似于以下内容

[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
grpcio = "*"
grpcio-tools = "*"

[dev-packages]
pytest = "*"
pytest-asyncio = "*"

[requires]
python_version = "3.8"
python_full_version = "3.8.2"

9. 定义项目结构。例如:

“protos”——我们将放置 .proto 文件的文件夹

“src”——所有客户端代码都将位于其中

“tests” — 包含测试的文件夹

“utils”——不同的测试助手

图片

生成 Python GRPC 文件并创建客户端

  • 要从 .proto 文件生成 Python gRPC 文件,可以使用protoc带有 Python gRPC 插件的编译器。以下是生成它的命令

python -m grpc_tools.protoc -I./protos --python_out=./src/generated --grpc_python_out=./src/generated ./protos/users.proto

此命令将在“src/generated”文件夹中生成 GRPC 客户端文件

图片

  • 接下来我们将创建一个非常简单的客户端

- 让我们创建一个文件“grpc_client.py”

- 让我们在里面放一个简单的实现

import grpc
import os
import sys

# Assuming the grpc_client.py is in src/client and the generated files are in src/generated
generated_files_directory = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'generated'))
if generated_files_directory not in sys.path:
    sys.path.append(generated_files_directory)

from users_pb2_grpc import UserStub

async def create_grpc_channel(address='localhost:5000'):
    """Create and return a gRPC channel."""
    return grpc.aio.insecure_channel(address)

def get_user_client(channel):
    """Create and return a user service client."""
    return UserStub(channel)

图片

  • 是时候创建测试了

图片

测试设计

1.首先,我们将创建一个测试配置文件,在其中定义频道和用户客户端

导入系统导入操作系统
import sys
import os
import pytest
base_directory = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
client_directory = os.path.join(base_directory, 'src', 'client')
generated_directory = os.path.join(base_directory, 'src', 'generated')

sys.path.append(client_directory)
sys.path.append(generated_directory)
from grpc_client import create_grpc_channel, get_user_client


@pytest.fixture(scope="function")
async def grpc_test_client():
    # Create the channel
    channel = await create_grpc_channel('localhost:5000') # I'm lazy, so I'm hardcoding the port for this example. It should be read from a config file

    # Create the client
    client = get_user_client(channel)

    # Provide the client to the test and ensure it is awaited properly
    try:
        yield client
    finally:
        # Close the channel after the test

        await channel.close()

图片

2. 最后我们可以创建测试。

我将添加正面和负面的案例

import pytest
from users_pb2 import UserRequestModel
from google.protobuf.wrappers_pb2 import Int32Value
from utils.randomizers.random_string import generate_random_string
from grpc import StatusCode, RpcError

@pytest.mark.asyncio
async def test_create_user_with_full_valid_data_should_be_created(grpc_test_client):
    # Arrange
    user_client = grpc_test_client

    request_model = UserRequestModel(
        email="john@example.com",
        firstName="John",
        lastName="Doe",
        age=Int32Value(value=30),
        isDiscount=True,
        userType=1
    )

    # Act
    response = await user_client.CreateUser(request_model)

    # Assert
    assert response is not None
    assert response.email == "john@example.com"
    assert response.firstName == "John"
    assert response.lastName == "Doe"
    assert response.age.value == 30
    assert response.isDiscount is True
    assert response.id > 0


@pytest.mark.asyncio
@pytest.mark.parametrize("chars_number, is_positive", [
    (3, True),
    (2, False),
    (100, True),
    (101, False)
])
async def test_create_user_last_name_validation(grpc_test_client, chars_number, is_positive):
    # Arrange
    user_client = grpc_test_client
    request_model = UserRequestModel(
        email="test@test.com",
        firstName="FirstName",
        lastName=generate_random_string(chars_number)
    )

    # Act and Assert
    if is_positive:
        # Expect no exception, and user creation should be successful.
        response = await user_client.CreateUser(request_model)
        assert response is not None  # Ensure some response logic if needed
    else:
        # Expect an exception due to invalid last name length.
        with pytest.raises(RpcError) as exc_info:
            await user_client.CreateUser(request_model)

        assert exc_info.value.code() == StatusCode.INVALID_ARGUMENT
        if chars_number == 101:
            expected_message = "Last name max length is 100"
        else:
            expected_message = "Last name min length is 3"

        assert expected_message in exc_info.value.details()

图片

注意:我知道这是偷懒的代码。我只是想和你分享一个基本的想法

3.当然,让我们运行测试

  • 使用以下命令激活虚拟环境:

pipenv shell
  • 使用命令运行测试:

pytest

图片

结语

在本文中,我们简要回顾了如何使用 Postman、NET 和 Python 客户端测试 GRPC 服务。我想指出的是,有些部分没有包括在内,这些部分与发送和接收流作为参数有关。此类流有助于发送和接收任何数据类型的消息。也许这在未来会是一个很好的话题。

行动吧,在路上总比一直观望的要好,未来的你肯定会感 谢现在拼搏的自己!如果想学习提升找不到资料,没人答疑解惑时,请及时加入群: 759968159,里面有各种测试开发资料和技术可以一起交流哦。

最后: 下方这份完整的软件测试视频教程已经整理上传完成,需要的朋友们可以自行领取 【保证100%免费】

在这里插入图片描述

 ​​​​软件测试面试文档

我们学习必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有字节大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。

在这里插入图片描述

在这里插入图片描述

  • 10
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
以下是使用 gRPC 的 C++ API 中的 `grpc::ServerBuilder` 构建 gRPC 服务器的一个简单示例: ```cpp #include <iostream> #include <memory> #include <string> #include <grpcpp/grpcpp.h> #include "greeter.grpc.pb.h" using grpc::Server; using grpc::ServerBuilder; using grpc::ServerContext; using grpc::Status; using helloworld::Greeter; using helloworld::HelloReply; using helloworld::HelloRequest; // 实现 Greeter 服务 class GreeterServiceImpl final : public Greeter::Service { Status SayHello(ServerContext* context, const HelloRequest* request, HelloReply* reply) override { std::string prefix("Hello "); reply->set_message(prefix + request->name()); return Status::OK; } }; void RunServer() { std::string server_address("0.0.0.0:50051"); GreeterServiceImpl service; // 创建 ServerBuilder 对象并指定服务器地址 ServerBuilder builder; builder.AddListeningPort(server_address, grpc::InsecureServerCredentials()); // 将 Greeter 服务添加到 gRPC 服务器中 builder.RegisterService(&service); // 构建服务器并启动 std::unique_ptr<Server> server(builder.BuildAndStart()); std::cout << "Server listening on " << server_address << std::endl; // 等待服务器关闭 server->Wait(); } int main(int argc, char** argv) { RunServer(); return 0; } ``` 在上面的示例中,我们首先实现了 `Greeter::Service`,并在其中实现了 `SayHello` 方法。然后,我们创建了一个 `GreeterServiceImpl` 对象,并将其注册到 `ServerBuilder` 中,使用 `AddListeningPort` 方法指定服务器地址和安全凭证,最后使用 `BuildAndStart` 方法构建 gRPC 服务器并启动。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值