RSS

在 gRPC JUnit 测试中优雅地清理

最佳实践是,当 gRPC 资源(如客户端通道、服务器以及先前附加的上下文)不再需要时,始终对其进行清理。

即使在 JUnit 测试中也是如此。否则,泄漏的资源不仅会永久驻留在您的机器上,还可能干扰后续的测试。比较轻微的情况是,由于上一个测试留下的泄漏资源,导致后续测试无法通过。最糟糕的情况是,某些后续测试原本应该失败,却因为之前的测试未清理资源而通过了测试。

所以,清理、清理、再清理……如果清理不成功,请直接令测试失败。

一个典型的示例如下:

public class MyTest {
  private Server server;
  private ManagedChannel channel;
  ...
  @After
  public void tearDown() throws InterruptedException {
    // assume channel and server are not null
    channel.shutdownNow();
    server.shutdownNow();
    // fail the test if cleanup is not successful
    assert channel.awaitTermination(5, TimeUnit.SECONDS) : "channel failed to shutdown";
    assert server.awaitTermination(5, TimeUnit.SECONDS) : "server failed to shutdown";
  }
  ...
}

或者更优雅的做法:

public class MyTest {
  private Server server;
  private ManagedChannel channel;
  ...
  @After
  public void tearDown() throws InterruptedException {
    // assume channel and server are not null
    channel.shutdown();
    server.shutdown();
    // fail the test if cannot gracefully shutdown
    try {
      assert channel.awaitTermination(5, TimeUnit.SECONDS) : "channel cannot be gracefully shutdown";
      assert server.awaitTermination(5, TimeUnit.SECONDS) : "server cannot be gracefully shutdown";
    } finally {
      channel.shutdownNow();
      server.shutdownNow();
    }
  }
  ...
}

然而,必须在每个测试中添加所有这些代码以实现优雅关闭,会增加您的工作量,因为您需要自己编写关闭的样板代码。因此,gRPC 测试库提供了辅助规则来减轻这项工作。

最初,我们引入了 JUnit 规则 GrpcServerRule 来消除这些关闭样板代码。该规则在测试开始时创建一个进程内(In-Process)服务器和通道,并在测试结束时自动将其关闭。然而,用户发现此规则限制过多,因为它不支持进程内传输以外的其他传输方式,不支持到服务器的多个通道,不支持自定义通道或服务器构建器选项,也不支持在单个测试方法内进行配置。

在 gRPC v1.13 版本中,引入了一个更灵活的 JUnit 规则 GrpcCleanupRule,它同样消除了关闭样板代码。但与 GrpcServerRule 不同,GrpcCleanupRule 不会自动创建任何服务器或通道。用户可以像在普通测试中那样自行创建和启动服务器,并自行创建通道。有了这个规则,用户只需要注册每个需要在测试结束时关闭的资源(通道或服务器),规则就会自动以优雅的方式将它们关闭。

您可以在运行测试方法之前注册资源:

public class MyTest {
  @Rule
  public GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
  ...
  private String serverName = InProcessServerBuilder.generateName();
  private Server server = grpcCleanup.register(InProcessServerBuilder
      .forName(serverName).directExecutor().addService(myServiceImpl).build().start());
  private ManagedChannel channel = grpcCleanup.register(InProcessChannelBuilder
      .forName(serverName).directExecutor().build());
  ...
}

或者在每个单独的测试方法内注册:

public class MyTest {
  @Rule
  public GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
  ...
  private String serverName = InProcessServerBuilder.generateName();
  private InProcessServerBuilder serverBuilder = InProcessServerBuilder
      .forName(serverName).directExecutor();
  private InProcessChannelBuilder channelBuilder = InProcessChannelBuilder
      .forName(serverName).directExecutor();
  ...

  @Test
  public void testFooBar() {
    ...
    grpcCleanup.register(
    	serverBuilder.addService(myServiceImpl).build().start());
    ManagedChannel channel = grpcCleanup.register(
    	channelBuilder.maxInboundMessageSize(1024).build());
    ...
  }
}

现在有了 GrpcCleanupRule,您无需再担心 JUnit 测试中 gRPC 服务器和通道的优雅关闭问题。快来试试它,清理您的测试代码吧!