Labs SD >

Tratamento de erros com gRPC e testes de integração com JUnit

Objetivos da semana

Materiais de apoio à aula

Exemplos:

 


Exercício

Implementação da operação ctrlPing no projeto (P1)

O objetivo do exercício é construir e testar uma primeira operação remota no projeto.
Para este fim, vai-se:

  1. Definir o ctrl_ping no contrato, implementar no servidor e testar manualmente com o cliente;
  2. Enviar informação de erro do servidor para o cliente;
  3. Testar a operação do servidor a partir do cliente com testes de integração JUnit 5.

Vamos então começar!

O ponto de partida é o código base que está no repositório GitHub de cada grupo, que deve ser obtido com o comando git clone.

Antes de começar, e para prevenir conflitos nos nomes dos módulos Maven, deve alterar os pom.xml e README.md, substituindo todas as referências a CXX pelo identificador do grupo. Por exemplo, o grupo A22 corresponde ao grupo 22 sediado no campus Alameda; já o grupo T07 corresponde ao grupo 7 sediado no Taguspark.

  1. Definir, implementar e experimentar a operação remota ctrl_ping
    1. Existem três módulos: contract, server e client. Vamos começar pelo contrato:
      1. Aceder ao ficheiro .proto. Este ficheiro define as estruturas de dados e as operações remotas.
      2. Cada operação necessita de uma mensagem de pedido e de uma mensagem de resposta.
        • Definir a mensagem do pedido, PingRequest, que recebe texto simples.
        • Definir a mensagem do resposta, PingResponse, que devolve também texto simples.
        • Definir a operação rpc com o nome ctrl_ping, com os tipos de pedido e resposta indicados.
      3. Vamos gerar código Java a partir dos protocol buffers. O Maven está configurado com plug-ins para chamar a ferramenta protoc (protocol buffers compiler)
        • Executar: mvn install
        • Se tudo correr bem, as definições protobuf foram convertidas para classes Java, que foram compiladas e instaladas no repositório local do Maven.
        • Para consultar o código gerado, faça refresh (right-click, Maven, Update Project, Force Update of Snapshots/Releases, OK) no Eclipse e depois consulte os ficheiros na pasta: target/generated-sources/protobuf.
          Há classes que representam os tipos de dados das mensagens e há classes de suporte ao servidor e ao cliente do RPC.

    2. Vamos agora concretizar o servidor:
      1. As classes de domínio da aplicação, onde são definidas as entidades e os comportamentos, ficam no pacote domain.
        • Identifique o Domain Root e as restantes entidades representadas nas classes.
        • Veja os mecanismos de sincronização que são utilizados para garantir que as classes podem ser chamadas corretamente por múltiplas tarefas (threads).
        Para o ctrl_ping não deve ser necessário usar o domínio, mas para as operações principais será necessário.
      2. Com base nas classes geradas pelo protoc e, por analogia com outros exemplos de servidores gRPC já vistos nas aulas, crie a classe ...ServerImpl e adicione o método de implementação do ctrl_ping:
        public void ctrlPing(PingRequest request, 
            StreamObserver<PingResponse> responseObserver) {
        
            String input = request.getInputText();
            String output = "Hello " + input + "!";
            PingResponse response = PingResponse.newBuilder().
                setOutputText(output).build();
            responseObserver.onNext(response);
            responseObserver.onCompleted();
        }
                                        
      3. Ajuste a classe ServerApp para instanciar o servidor e direcionar os pedidos para a implementação.
      4. Para compilar e testar, fazer: mvn compile exec:java

    3. Vamos agora concretizar o cliente:
      1. O ...Frontend vai envolver o channel e o stub gerado pelo protoc. Todos os pedidos para o servidor vão depois usar este objeto ...Frontend, o que permitirá mais tarde acrescentar funcionalidades.
      2. O objeto ...ClientApp deverá criar um frontend e fazer uma chamada à operação remota:
            ServerFrontend frontend = new ServerFrontend(host, port);
        
            ...
        
            PingRequest request = PingRequest.newBuilder().setInputText("friend").build();
            PingResponse response = frontend.ctrlPing(request);
            System.out.println(response);
                                
      3. Para compilar e testar:
        mvn compile exec:java
        Se tudo correr bem, deverá aparecer a mensagem: Hello friend!

  2. Enviar informação de erro do servidor para o cliente
    1. Vamos adicionar um retorno de erro ao servidor caso a mensagem do pedido seja vazia.
      Importar a definição de um estado de erro para argumentos inválidos:
      import static io.grpc.Status.INVALID_ARGUMENT;
      ...
                      
    2. Verificar se o argumento é vazio e devolver o erro em caso afirmativo.
          ...
          if (inputText == null || inputText.isBlank()) {
              responseObserver.onError(INVALID_ARGUMENT
                  .withDescription("Input cannot be empty!").asRuntimeException());
          }
          ...
                      
    3. Do lado do cliente, vamos apanhar uma exceção e imprimir a mensagem de erro:
          try {
              PingRequest request = PingRequest.newBuilder().setInputText("").build();
              PingResponse response = frontend.ctrlPing(request);
      
          } catch (StatusRuntimeException e) {
              System.out.println("Caught exception with description: " + 
                  e.getStatus().getDescription());
          }
                      

  3. Testar a operação do servidor a partir do cliente
    1. Vamos agora criar um teste de integração para verificar este comportamento do servidor.
      Criar a classe PingIT na pasta src/test/java/, seguindo a convenção de uma pasta por cada pacote (package) Java.
    2. Primeiro vamos criar um teste para o caso normal:
          @Test
          public void pingOKTest() {
              PingRequest request = PingRequest.newBuilder().setInputText("friend").build();
              PingResponse response = frontend.ctrlPing(request);
              assertEquals("Hello friend!", response.getOutputText());
          }
                      
    3. Para correr os testes, lançar o servidor primeiro, e depois fazer: mvn verify
      O resultado deverá ser bem sucedido para todos os testes.

    4. Vamos de seguida criar um segundo teste, para o caso de erro:
          @Test
          public void emptyPingTest() {
              PingRequest request = PingRequest.newBuilder().setInputText("").build();
              assertEquals(
                  INVALID_ARGUMENT.getCode(),
                  assertThrows(
                          StatusRuntimeException.class, () -> frontend.ctrlPing(request))
                      .getStatus()
                      .getCode());
          }
                      
    5. Correr novamente os testes com mvn verify.
      O resultado deverá ser, novamente, bem sucedido para todos os testes. Repare que este segundo teste tem sucesso se houver exceção, porque é isso que é esperado.

 

Próximos passos

 


© Docentes de Sistemas Distribuídos, Dep. Eng. Informática, Técnico Lisboa