天天看點

用于單元測試的模拟 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 上找到代碼!

繼續閱讀