天天看点

用于单元测试的模拟 gRPC 服务

每日分享最新,最流行的软件开发知识与最新行业趋势,希望大家能够一键三连,多多支持,跪求关注,点赞,留言。

#双十一好物狂欢节#

用于单元测试的模拟 gRPC 服务
在我们的日常工作中,我们开发的应用程序包括通过 I/O 与软件组件进行交互。它们可以是数据库、代理或某种形式的 Blob 存储。以您与之交互的云组件为例:Azure 存储队列、SQS、Pub/Sub。与这些组件的通信通常通过 SDK 进行。

从一开始,测试就会开始。因此,与这些组件的交互应该在测试环境中处理。一种方法是使用这些组件的安装(或模拟器)并让代码与实际实例交互,就像通过使用测试容器或创建仅用于测试目的的基础设施来实现的方式一样。

另一种方法是启动组件的模拟服务并让测试与之交互。Hoverfly就是一个很好的例子。一个模拟的 HTTP 服务在测试期间运行,测试用例与之交互。

根据我们的测试过程所需的质量,两者都可以在各种情况下使用。我们将重点关注应用于gRPC的第二种方法。

众所周知,大多数Google Cloud 组件都带有 gRPC API。在我们的场景中,我们有一个向 Pub/Sub 发布消息的应用程序。

用于单元测试的模拟 gRPC 服务

让我们首先放置所需的依赖项:

XML

<dependencyManagement>

<dependencies>

<dependency>

<groupId>com.google.cloud</groupId>

<artifactId>libraries-bom</artifactId>

<version>24.1.2</version>

<type>pom</type>

<scope>import</scope>

</dependency>

</dependencies>

</dependencyManagement>

<dependencies>

<dependency>

<groupId>com.google.cloud</groupId>

<artifactId>google-cloud-pubsub</artifactId>

</dependency>

<dependency>

<groupId>io.grpc</groupId>

<artifactId>grpc-testing</artifactId>

<version>1.43.2</version>

<scope>test</scope>

</dependency>

<dependency>

<groupId>com.google.api.grpc</groupId>

<artifactId>grpc-google-cloud-pubsub-v1</artifactId>

<version>1.97.1</version>

<scope>test</scope>

</dependency>

</dependencies>

让我们从我们的发布者类开始。

package com.egkatzioura.notification.publisher;

import java.util.concurrent.CompletableFuture;

import java.util.concurrent.Executor;

import com.google.api.core.ApiFuture;

import com.google.api.core.ApiFutureCallback;

import com.google.api.core.ApiFutures;

import com.google.cloud.pubsub.v1.Publisher;

import com.google.protobuf.ByteString;

import com.google.pubsub.v1.PubsubMessage;

public class UpdatePublisher {

private final Publisher publisher;

private final Executor executor;

public UpdatePublisher(Publisher publisher, Executor executor) {

this.publisher = publisher;

this.executor = executor;

}

public CompletableFuture<String> update(String notification) {

PubsubMessage pubsubMessage = PubsubMessage.newBuilder()

.setData(ByteString.copyFromUtf8(notification))

.build();

ApiFuture<String> apiFuture = publisher.publish(pubsubMessage);

return toCompletableFuture(apiFuture);

}

private CompletableFuture<String> toCompletableFuture(ApiFuture<String> apiFuture) {

final CompletableFuture<String> responseFuture = new CompletableFuture<>();

ApiFutures.addCallback(apiFuture, new ApiFutureCallback<>() {

@Override

public void onFailure(Throwable t) {

responseFuture.completeExceptionally(t);

}

@Override

public void onSuccess(String result) {

responseFuture.complete(result);

}

}, executor);

return responseFuture;

}

}

发布者将发送消息并返回所发送消息 ID 的 CompletableFuture。

所以让我们测试这个类。我们的目标是发送消息并取回消息 ID。模拟和模拟的服务是 Pub/Sub。

为此,我们添加了对 Maven 的 gRPC API 依赖。

<dependency>

<groupId>com.google.api.grpc</groupId>

<artifactId>grpc-google-cloud-pubsub-v1</artifactId>

<version>1.97.1</version>

<scope>test</scope>

</dependency>

我们将模拟用于发布操作的 API。要实现的类是 PublisherGrpc.PublisherImplBase。

package com.egkatzioura.notification.publisher;

import java.util.UUID;

import com.google.pubsub.v1.PublishRequest;

import com.google.pubsub.v1.PublishResponse;

import com.google.pubsub.v1.PublisherGrpc;

import io.grpc.stub.StreamObserver;

public class MockPublisherGrpc extends PublisherGrpc.PublisherImplBase {

private final String prefix;

public MockPublisherGrpc(String prefix) {

this.prefix = prefix;

}

@Override

public void publish(PublishRequest request, StreamObserver<PublishResponse> responseObserver) {

responseObserver.onNext(PublishResponse.newBuilder().addMessageIds(prefix+":"+UUID.randomUUID().toString()).build());

responseObserver.onCompleted();

}

}

如您所见,消息 ID 将具有我们定义的前缀。

这将是服务器端的 PublisherGrpc 实现。让我们继续进行单元测试。UpdatePublisher 类可以注入一个 Publisher。此发布者将被配置为使用之前创建的 PublisherGrpc.PublisherImplBase。

@Rule

public final GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();

private static final String MESSAGE_ID_PREFIX = "message";

@Before

public void setUp() throws Exception {

String serverName = InProcessServerBuilder.generateName();

Server server = InProcessServerBuilder

.forName(serverName).directExecutor().addService(new MockPublisherGrpc(MESSAGE_ID_PREFIX)).build().start();

grpcCleanup.register(server);

...

上面我们创建了一个为进程内请求提供服务的 GRPC 服务器。然后我们注册了之前创建的模拟服务。

向前!我们使用该服务创建 Publisher 并创建要测试的类的实例。

@Before

public void setUp() throws Exception {

String serverName = InProcessServerBuilder.generateName();

Server server = InProcessServerBuilder

.forName(serverName).directExecutor().addService(new MockPublisherGrpc(MESSAGE_ID_PREFIX)).build().start();

grpcCleanup.register(server);

ExecutorProvider executorProvider = testExecutorProvider();

ManagedChannel managedChannel = InProcessChannelBuilder.forName(serverName).directExecutor().build();

TransportChannel transportChannel = GrpcTransportChannel.create(managedChannel);

TransportChannelProvider transportChannelProvider = FixedTransportChannelProvider.create(transportChannel);

String topicName = "projects/test-project/topic/my-topic";

Publisher publisher = Publisher.newBuilder(topicName)

.setExecutorProvider(executorProvider)

.setChannelProvider(transportChannelProvider)

.build();

updatePublisher = new UpdatePublisher(publisher, Executors.newSingleThreadExecutor());

...

我们将 Channel 传递给指向 InProcessServer 的发布者。请求将被路由到我们注册的服务。最后,我们可以添加我们的测试。

@Test

public void testPublishOrder() throws ExecutionException, InterruptedException {

String messageId = updatePublisher.update("Some notification").get();

assertThat(messageId, containsString(MESSAGE_ID_PREFIX));

}

我们做到了!我们创建了进程内 gRPC 服务器,以便对 gRPC 驱动的服务进行测试。

你可以在GitHub 上找到代码!

继续阅读