测试是软件开发中重要的一步,本文介绍如何创建grpc的测试来保证代码的质量。这里都沿用之前的server和client代码

服务端测试

public class HelloWorldServerTest {
  //定义服务名字用于绑定客户端调用的服务端
  private static final String UNIQUE_SERVER_NAME = "in-process server for " + HelloWorldServerTest.class;
  //创建一个进程内的server
  private final Server inProcessServer = InProcessServerBuilder.forName(UNIQUE_SERVER_NAME).addService(new HelloServiceImpl()).directExecutor().build();
  //创建一个进程内的channel
  private final ManagedChannel inProcessChannel = InProcessChannelBuilder.forName(UNIQUE_SERVER_NAME).directExecutor().build();

  /**
   * 初始化进城内服务器
   */
  @Before
  public void setUp() throws Exception {
    inProcessServer.start();
  }

  /**
   * 使用stub调用server并验证返回结果
   */
  @Test
  public void testSimple() throws Exception {
    HelloServiceGrpc.HelloServiceBlockingStub blockingStub = HelloServiceGrpc.newBlockingStub(inProcessChannel);
    String testName = "World";
    //调用服务
    blog.proto.ProtoObj.Result reply = blockingStub.simpleHello(ProtoObj.Person.newBuilder().setMyName(testName).build());
	//验证·
    assertEquals("hello, " + testName, reply.getString());
  }
  
  /**
   * 关闭服务器和channel
   */
  @After
  public void tearDown() {
    inProcessChannel.shutdownNow();
    inProcessServer.shutdownNow();
  }
}

客户端测试

在客户端测试之前,需要对之前的客户端进行一些改动,需要将channel变为实例变量,方便注入,这里以simple服务为例:

public class HelloClient {
    private static final Logger logger = Logger.getLogger(HelloClient.class.getName());
	//提出的channel
    private final ManagedChannel channel;
	//stub
    private final HelloServiceGrpc.HelloServiceBlockingStub blockingStub;
	//在构造方法中传入channel
    public HelloClient(ManagedChannel channel){
        this.channel=channel;
        blockingStub = HelloServiceGrpc.newBlockingStub(channel);
    }

    public void simple(String name) {
        logger.info("Will try to greet " + name + " ...");
        ProtoObj.Person person = ProtoObj.Person.newBuilder().setMyName(name).build();
        try {
            ProtoObj.Result response = blockingStub.simpleHello(person);
            logger.info(response.getString());
        } catch (StatusRuntimeException e) {
            logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
            return;
        }
    }
}

之后进行测试:

public class HelloWorldClientTest {
  //spy一个服务实例
  private final HelloServiceGrpc.HelloServiceImplBase serviceImpl = spy(new HelloServiceGrpc.HelloServiceImplBase() {
  });

  private Server fakeServer;
  private HelloClient client;
  ManagedChannel channel;
  /**
   * 模拟server和channel并创建client
   */
  @Before
  public void setUp() throws Exception {
    String uniqueServerName = "fake server.java for " + getClass();
	//创建进程内服务和channel
    fakeServer = InProcessServerBuilder.forName(uniqueServerName).directExecutor().addService(serviceImpl).build().start();
    InProcessChannelBuilder channelBuilder = InProcessChannelBuilder.forName(uniqueServerName).directExecutor();
    channel=channelBuilder.build();
	//创建client
    client = new HelloClient(channel);
  }

  @Test
  public void greet_messageDeliveredToServer() {
    ArgumentCaptor< ProtoObj.Person> requestCaptor = ArgumentCaptor.forClass( ProtoObj.Person.class);
    String testName = "world";

    client.simple(testName);
	//验证服务器期望收到的参数
    verify(serviceImpl).simpleHello(requestCaptor.capture(), Matchers.<StreamObserver<ProtoObj.Result>>any());
    assertEquals(testName, requestCaptor.getValue().getMyName());
  }
  
  @After
  public void tearDown() throws Exception {
	//关闭channel和server
    channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
    fakeServer.shutdownNow();
  }
}

总结

在单元测试时一大麻烦的是多线程的处理,使用InProcessServerBuilder会创建进程内的调用,server和client将同步在main线程中调用,这样就减少了多线程、异步测试会遇到的问题。