Unit testing 如何对grpc java服务器实现功能进行单元测试?
我有一个GRPCJava服务器代码的实现,但是我没有找到对StreamObserver进行单元测试的示例代码。有人知道单元测试函数的正确方法吗Unit testing 如何对grpc java服务器实现功能进行单元测试?,unit-testing,grpc,Unit Testing,Grpc,我有一个GRPCJava服务器代码的实现,但是我没有找到对StreamObserver进行单元测试的示例代码。有人知道单元测试函数的正确方法吗 public class RpcTrackDataServiceImpl implements TrackDataServiceGrpc.TrackDataService { @Override public void getTracks(GetTracksRequest request, StreamObserver < GetT
public class RpcTrackDataServiceImpl implements TrackDataServiceGrpc.TrackDataService {
@Override
public void getTracks(GetTracksRequest request, StreamObserver < GetTracksResponse > responseObserver) {
GetTracksResponse reply = GetTracksResponse
.newBuilder()
.addTracks(TrackInfo.newBuilder()
.setOwner("test")
.setTrackName("test")
.build())
.build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
公共类RpcTrackDataServiceImpl实现TrackDataServiceGrpc.TrackDataService{
@凌驾
public void gettrack(GetTracksRequest请求,StreamObserverresponseObserver){
GetTracksResponse reply=GetTracksResponse
.newBuilder()
.addTracks(TrackInfo.newBuilder()
.setOwner(“测试”)
.setTrackName(“测试”)
.build())
.build();
responseObserver.onNext(回复);
responseObserver.onCompleted();
}
}
我建议使用进程内传输。进程内传输非常轻量级,但也使用了大量“真实”代码,因此其行为与真实传输非常匹配。如果还对通道和服务器使用directExecutor()
,那么测试基本上是单线程的,并且是确定性的。(尽管另一个线程仍将用于处理截止日期。)
尽管问题在于对服务进行单元测试,但InProcess对于对客户端进行单元测试也很有用。我最终找到了一个解决方案,创建了一个实现StreamObserver接口的FakeStreamMobServer。
传入FakeStreamMobServer以执行onNext、onCompleted等操作。
我不确定这是否是最好的方法。使用上面Eric提到的进程内传输进行单元测试非常简单。下面是一个代码更明确的示例: 我们基于以下protobuff定义测试服务:
syntax = "proto3";
option java_multiple_files = true;
option java_package = "servers.dummy";
option java_outer_classname = "DummyProto";
option objc_class_prefix = "DMYS";
package dummy;
import "general.proto";
// The dummy service definition.
service DummyService {
// # Misc
// Returns the server version
rpc getVersion (Empty) returns (ServerVersion) {}
// Returns the java version
rpc getJava (Empty) returns (JavaVersion) {}
}
// Transmission data types
(上面包含以下文件:)
基于Protoc编译器生成的Java的DummyService如下所示:
package servers.dummy;
import java.util.logging.Logger;
import general.Empty;
import general.JavaVersion;
import general.ServerVersion;
import io.grpc.stub.StreamObserver;
public class DummyService extends DummyServiceGrpc.DummyServiceImplBase {
private static final Logger logger = Logger.getLogger(DummyService.class.getName());
@Override
public void getVersion(Empty req, StreamObserver<ServerVersion> responseObserver) {
logger.info("Server Version-Request received...");
ServerVersion version = ServerVersion.newBuilder().setVersion("1.0.0").build();
responseObserver.onNext(version);
responseObserver.onCompleted();
}
@Override
public void getJava(Empty req, StreamObserver<JavaVersion> responseObserver) {
logger.info("Java Version Request received...");
JavaVersion version = JavaVersion.newBuilder().setVersion(Runtime.class.getPackage().getImplementationVersion() + " (" + Runtime.class.getPackage().getImplementationVendor() + ")").build();
responseObserver.onNext(version);
responseObserver.onCompleted();
}
}
packageservers.dummy;
导入java.util.logging.Logger;
导入一般。空;
导入general.JavaVersion;
导入general.ServerVersion;
导入io.grpc.stub.StreamObserver;
公共类DummyService扩展了DummyServiceGrpc.DummyServiceImplBase{
私有静态最终记录器Logger=Logger.getLogger(DummyService.class.getName());
@凌驾
public void getVersion(空请求,StreamObserver responseObserver){
logger.info(“收到服务器版本请求…”);
ServerVersion version=ServerVersion.newBuilder().setVersion(“1.0.0”).build();
responseObserver.onNext(版本);
responseObserver.onCompleted();
}
@凌驾
public void getJava(空请求,StreamObserver responseObserver){
info(“收到Java版本请求…”);
JavaVersion version=JavaVersion.newBuilder().setVersion(Runtime.class.getPackage().getImplementationVersion()+”(“+Runtime.class.getPackage().getImplementationVendor()+”)).build();
responseObserver.onNext(版本);
responseObserver.onCompleted();
}
}
现在,我们构建一个InProcessServer,它运行我们的虚拟服务(或您想要测试的任何其他服务):
包服务器;
导入io.grpc.Server;
导入io.grpc.inprocess.InProcessServerBuilder;
导入java.io.IOException;
导入java.util.logging.Logger;
导入servers.util.PortServer;
/**
*InProcessServer,用于管理与客户端正在运行的同一进程中的服务的启动/关闭。用于单元测试目的。
*@author be
*/
进程服务器中的公共类{
私有静态最终记录器Logger=Logger.getLogger(PortServer.class.getName());
专用服务器;
私人课堂;
公共InProcessServer(clazz类){
this.clazz=clazz;
}
public void start()引发IOException、InstanceionException、IllegalAccessException{
服务器=InProcessServerBuilder
.forName(“测试”)
.directExecutor()
.addService(clazz.newInstance())
.build()
.start();
info(“InProcessServer已启动”);
Runtime.getRuntime().addShutdownHook(新线程(){
@凌驾
公开募捐{
//在这里使用stderr,因为记录器可能已被其JVM关闭挂钩重置。
System.err.println(“***正在关闭gRPC服务器,因为JVM正在关闭”);
InProcessServer.this.stop();
System.err.println(“***服务器关闭”);
}
});
}
无效停止(){
如果(服务器!=null){
server.shutdown();
}
}
/**
*等待主线程上的终止,因为grpc库使用守护进程线程。
*/
public void blockuntlshutdown()引发InterruptedException{
如果(服务器!=null){
等待终止();
}
}
}
现在,我们可以使用以下单元测试来测试服务:
package servers;
import static org.junit.Assert.*;
import general.ServerVersion;
import io.grpc.ManagedChannel;
import io.grpc.StatusRuntimeException;
import io.grpc.inprocess.InProcessChannelBuilder;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import servers.dummy.DummyService;
import servers.dummy.DummyServiceGrpc;
import servers.dummy.DummyServiceGrpc.DummyServiceBlockingStub;
import servers.dummy.DummyServiceGrpc.DummyServiceStub;
public class InProcessServerTest {
private static final Logger logger = Logger.getLogger(InProcessServerTest.class.getName());
private InProcessServer<DummyService> inprocessServer;
private ManagedChannel channel;
private DummyServiceBlockingStub blockingStub;
private DummyServiceStub asyncStub;
public InProcessServerTest() {
super();
}
@Test
public void testInProcessServer() throws InterruptedException{
try {
String version = getServerVersion();
assertEquals("1.0.0", version);
} finally {
shutdown();
}
}
/** Ask for the server version */
public String getServerVersion() {
logger.info("Will try to get server version...");
ServerVersion response;
try {
response = blockingStub.getVersion(null);
} catch (StatusRuntimeException e) {
logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
fail();
return "";
}
return response.getVersion();
}
@Before
public void beforeEachTest() throws InstantiationException, IllegalAccessException, IOException {
inprocessServer = new InProcessServer<DummyService>(DummyService.class);
inprocessServer.start();
channel = InProcessChannelBuilder
.forName("test")
.directExecutor()
// Channels are secure by default (via SSL/TLS). For the example we disable TLS to avoid
// needing certificates.
.usePlaintext(true)
.build();
blockingStub = DummyServiceGrpc.newBlockingStub(channel);
asyncStub = DummyServiceGrpc.newStub(channel);
}
@After
public void afterEachTest(){
channel.shutdownNow();
inprocessServer.stop();
}
public void shutdown() throws InterruptedException {
channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
}
}
public class RpcTrackDataServiceImpl implements TrackDataServiceGrpc.TrackDataService {
@Override
public void getTracks(GetTracksRequest request, StreamObserver<GetTracksResponse> responseObserver) {
GetTracksResponse reply = getTracks(request);
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
@VisibleForTesting
GetTracksResponse getTracks(GetTracksRequest request) {
return GetTracksResponse
.newBuilder()
.addTracks(TrackInfo.newBuilder()
.setOwner("test")
.setTrackName("test")
.build())
.build();
}
}
包服务器;
导入静态org.junit.Assert.*;
导入general.ServerVersion;
导入io.grpc.ManagedChannel;
导入io.grpc.StatusRuntimeException;
导入io.grpc.inprocess.InProcessChannelBuilder;
导入java.io.IOException;
导入java.util.concurrent.TimeUnit;
导入java.util.logging.Level;
导入java.util.logging.Logger;
导入org.junit.After;
导入org.junit.Before;
导入org.junit.Test;
导入servers.dummy.DummyService;
导入servers.dummy.DummyServiceGrpc;
导入servers.dummy.DummyServiceGrpc.DummyServiceBlockingStub;
导入servers.dummy.DummyServiceGrpc.DummyServiceStub;
公共类InProcessServerTest{
私有静态最终记录器Logger=Logger.getLogger(InProcessServerTest.class.getName());
私有InProcessServer InProcessServer;
专用管理通道;
私有DummyServiceBlockingStub blockingStub;
私有DummyServiceStub异步存根;
公共InProcessServerTest(){
超级();
}
@试验
public void testInProcessServer()引发InterruptedException{
试一试{
字符串版本=getServerVersion();
assertEquals(“1.0.0”,版本);
}最后{
关机();
}
}
/**询问服务器版本*/
公共字符串getServerVersion(){
logger.info(“将尝试获取服务器
package servers;
import static org.junit.Assert.*;
import general.ServerVersion;
import io.grpc.ManagedChannel;
import io.grpc.StatusRuntimeException;
import io.grpc.inprocess.InProcessChannelBuilder;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import servers.dummy.DummyService;
import servers.dummy.DummyServiceGrpc;
import servers.dummy.DummyServiceGrpc.DummyServiceBlockingStub;
import servers.dummy.DummyServiceGrpc.DummyServiceStub;
public class InProcessServerTest {
private static final Logger logger = Logger.getLogger(InProcessServerTest.class.getName());
private InProcessServer<DummyService> inprocessServer;
private ManagedChannel channel;
private DummyServiceBlockingStub blockingStub;
private DummyServiceStub asyncStub;
public InProcessServerTest() {
super();
}
@Test
public void testInProcessServer() throws InterruptedException{
try {
String version = getServerVersion();
assertEquals("1.0.0", version);
} finally {
shutdown();
}
}
/** Ask for the server version */
public String getServerVersion() {
logger.info("Will try to get server version...");
ServerVersion response;
try {
response = blockingStub.getVersion(null);
} catch (StatusRuntimeException e) {
logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
fail();
return "";
}
return response.getVersion();
}
@Before
public void beforeEachTest() throws InstantiationException, IllegalAccessException, IOException {
inprocessServer = new InProcessServer<DummyService>(DummyService.class);
inprocessServer.start();
channel = InProcessChannelBuilder
.forName("test")
.directExecutor()
// Channels are secure by default (via SSL/TLS). For the example we disable TLS to avoid
// needing certificates.
.usePlaintext(true)
.build();
blockingStub = DummyServiceGrpc.newBlockingStub(channel);
asyncStub = DummyServiceGrpc.newStub(channel);
}
@After
public void afterEachTest(){
channel.shutdownNow();
inprocessServer.stop();
}
public void shutdown() throws InterruptedException {
channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
}
}
/*
* Copyright 2015, gRPC Authors All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.grpc.examples.helloworld;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;
import java.io.IOException;
import java.util.logging.Logger;
/**
* Server that manages startup/shutdown of a {@code Greeter} server.
*/
public class HelloWorldServer {
private static final Logger logger = Logger.getLogger(HelloWorldServer.class.getName());
private Server server;
private void start() throws IOException {
/* The port on which the server should run */
int port = 50051;
server = ServerBuilder.forPort(port)
.addService(new GreeterImpl())
.build()
.start();
logger.info("Server started, listening on " + port);
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
// Use stderr here since the logger may have been reset by its JVM shutdown hook.
System.err.println("*** shutting down gRPC server since JVM is shutting down");
HelloWorldServer.this.stop();
System.err.println("*** server shut down");
}
});
}
private void stop() {
if (server != null) {
server.shutdown();
}
}
/**
* Await termination on the main thread since the grpc library uses daemon threads.
*/
private void blockUntilShutdown() throws InterruptedException {
if (server != null) {
server.awaitTermination();
}
}
/**
* Main launches the server from the command line.
*/
public static void main(String[] args) throws IOException, InterruptedException {
final HelloWorldServer server = new HelloWorldServer();
server.start();
server.blockUntilShutdown();
}
static class GreeterImpl extends GreeterGrpc.GreeterImplBase {
@Override
public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
}
/*
* Copyright 2016, gRPC Authors All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.grpc.examples.helloworld;
import static org.junit.Assert.assertEquals;
import io.grpc.examples.helloworld.HelloWorldServer.GreeterImpl;
import io.grpc.testing.GrpcServerRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Unit tests for {@link HelloWorldServer}.
* For demonstrating how to write gRPC unit test only.
* Not intended to provide a high code coverage or to test every major usecase.
*
* <p>For more unit test examples see {@link io.grpc.examples.routeguide.RouteGuideClientTest} and
* {@link io.grpc.examples.routeguide.RouteGuideServerTest}.
*/
@RunWith(JUnit4.class)
public class HelloWorldServerTest {
/**
* This creates and starts an in-process server, and creates a client with an in-process channel.
* When the test is done, it also shuts down the in-process client and server.
*/
@Rule
public final GrpcServerRule grpcServerRule = new GrpcServerRule().directExecutor();
/**
* To test the server, make calls with a real stub using the in-process channel, and verify
* behaviors or state changes from the client side.
*/
@Test
public void greeterImpl_replyMessage() throws Exception {
// Add the service to the in-process server.
grpcServerRule.getServiceRegistry().addService(new GreeterImpl());
GreeterGrpc.GreeterBlockingStub blockingStub =
GreeterGrpc.newBlockingStub(grpcServerRule.getChannel());
String testName = "test name";
HelloReply reply = blockingStub.sayHello(HelloRequest.newBuilder().setName(testName).build());
assertEquals("Hello " + testName, reply.getMessage());
}
}
@RunWith(JUnit4.class)
public class HelloWorldServerTest {
/**
* This rule manages automatic graceful shutdown for the registered servers and channels at the
* end of test.
*/
@Rule
public final GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
/**
* To test the server, make calls with a real stub using the in-process channel, and verify
* behaviors or state changes from the client side.
*/
@Test
public void greeterImpl_replyMessage() throws Exception {
// Generate a unique in-process server name.
String serverName = InProcessServerBuilder.generateName();
// Create a server, add service, start, and register for automatic graceful shutdown.
grpcCleanup.register(InProcessServerBuilder
.forName(serverName).directExecutor().addService(new GreeterImpl()).build().start());
GreeterGrpc.GreeterBlockingStub blockingStub = GreeterGrpc.newBlockingStub(
// Create a client channel and register for automatic graceful shutdown.
grpcCleanup.register(InProcessChannelBuilder.forName(serverName).directExecutor().build()));
HelloReply reply =
blockingStub.sayHello(HelloRequest.newBuilder().setName( "test name").build());
assertEquals("Hello test name", reply.getMessage());
}
}
public class RpcTrackDataServiceImpl implements TrackDataServiceGrpc.TrackDataService {
@Override
public void getTracks(GetTracksRequest request, StreamObserver<GetTracksResponse> responseObserver) {
GetTracksResponse reply = getTracks(request);
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
@VisibleForTesting
GetTracksResponse getTracks(GetTracksRequest request) {
return GetTracksResponse
.newBuilder()
.addTracks(TrackInfo.newBuilder()
.setOwner("test")
.setTrackName("test")
.build())
.build();
}
}
public class UnitTest {
private final ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner();
@Configuration
public static class GetTracksConfiguration {
@Bean
public GetTracksService getTracksService() {
return new GetTracksService();
}
}
@Test
public void replyShouldBeSent() {
final GetTracksRequest request = GetTracksRequest.newBuilder().build();
final StreamObserver<GetTracksResponse> response = mock(StreamObserver.class);
applicationContextRunner
.withUserConfiguration(RequestTracksConfiguration.class)
.run(context -> {
assertThat(context)
.hasSingleBean(RequestTracksService.class);
context.getBean(RequestTracksService.class)
.getTracks(request, response);
verify(response, times(1)).onNext(any(GetTracksResponse.class));
verify(response, times(1)).onCompleted();
verify(response, never()).onError(any(Throwable.class));
});
}
@Test
public void shouldTestLogic {
assertLogicInFactoredOutMethodIsCorrect();
}
@RunWith(SpringRunner.class)
@SpringBootTest(
classes = {GetTracksService.class}
)
@EnableAutoConfiguration
public class SmokeTest {
private GetTracksServiceGrpc.GetTracksServiceBlockingStub blockingStub;
@Test
public void springClientConnects() {
final GetTracksRequest request = GetTracksRequest.newBuilder()
.build();
assertNotNull(blockingStub.getTracks(request));
}
}