Qualidade da sua API REST com REST-assured

rest-assured

Este é o primeiro post de uma série que visa ajudar os desenvolvedores a testarem efetivamente o código que produzem e, assim, fomentar a cultura da qualidade de software. As ferramentas que serão apresentadas testam uma aplicação exemplo de logística, chamada logistics, cujo código está disponível no nosso Github.

A aplicação permite criar mapas e rotas dentro destes mapas. O objetivo é encontrar o melhor caminho, entre o ponto de origem e o de destino, dada a autonomia do veículo e o preço do combustível. O melhor caminho é o mais barato. Uma API REST foi desenvolvida para expor as funcionalidades da aplicação, e vamos agora mostrar como testá-la com o REST-assured.

    <dependencies>
        <dependency>
            <groupId>com.jayway.restassured</groupId>
            <artifactId>rest-assured</artifactId>
            <version>2.5.0</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

Para testar, é preciso que a aplicação esteja disponível num servidor Java EE (particularmente, usamos o Wildfly). Por padrão, o REST-assured assume que a aplicação está disponível na porta 8080 do localhost, mas isso pode ser alterado definindo-se os campos baseURI e/ou port. No nosso teste, não precisamos alterar, então basta informar à ferramenta o endpoint:

    @Before
    public void setup() {
        RestAssured.basePath = "/logistics/api/maps";
    }

Este código faz parte do projeto logistics-test-restassured, com duas classes JUnit que utilizam o REST-assured: RESTTestRESTResponseSchemaTest. Ambas testam as funcionalidades da aplicação logistics na sequencia determinada pelos nomes dos métodos, comportamento definido pela annotation FixMethodOrder com o parâmetro MethodSorters.NAME_ASCENDING. A ordem dos testes é a seguinte:

  1. Criação de um mapa
  2. Verificação da existência do mapa recém criado
  3. Verificação da constraint de nome único para mapas
  4. Criação de rotas para o mapa
  5. Verificação da constraint de nome único para rotas
  6. Exclusão de uma rota
  7. Obtenção da melhor rota
  8. Exclusão do mapa

O REST-assured se baseia no modelo given-when-then para a realização dos testes. Segundo esse modelo, partindo de um cenário (given) e um comportamento (when) subsequente, teremos o resultado esperado (then). A diferença entre as duas classes é basicamente como avaliam o resultado esperado. Primeiro, vejamos como o teste da criação de um mapa é feito na RESTTest:

    @Test
    public void testA() {
        mapSlug = RestAssured
            .given()
                .contentType(ContentType.JSON)
                .body("{\"name\": \"REST-assured Test\"}")
            .when()
                .post()
            .then()
                .statusCode(200)
                .contentType(ContentType.JSON)
                .body("code", equalTo(200))
                .body("status", equalTo("success"))
                .body("data", notNullValue())
                .body("data.slug", notNullValue())
            .extract()
                .path("data.slug");
    }

Neste método, o primeiro da sequencia, é testada a criação de um mapa com o nome “REST-assured Test”, submetido no formato JSON via POST. É esperada uma resposta 200 do HTTP (sucesso) e o resultado retornado, também no formato JSON, representa o mapa recém criado no campo data, obrigatoriamente com o slug que o identifica definido. O slug é então extraído para uso nos testes posteriores (requer dependência abaixo).

        <dependency>
            <groupId>com.jayway.restassured</groupId>
            <artifactId>json-path</artifactId>
            <version>2.5.0</version>
            <scope>test</scope>
        </dependency>

Vejamos agora como o teste da criação de um mapa é feito na RESTResponseSchemaTest:

    @Test
    public void testA() {
        RestAssured
            .given()
                .contentType(ContentType.JSON)
                .body("{\"name\": \"REST-assured JSON Schema Test\"}")
            .when()
                .post()
            .then()
                .statusCode(200)
                .contentType(ContentType.JSON)
                .body(matchesJsonSchemaInClasspath("map-response-schema.json"));
    }

Observe que parte das mesmas condições, mas o resultado é avaliado de modo distinto. Como o resultado esperado está no formato JSON, é possível constrastá-lo com seu JSON schema, que nada mais é do que a descrição do JSON definido como resposta. Podemos dizer que o JSON schema está para o JSON assim como o XSD está para o XML. O REST-assured valida então se o JSON schema contido em map-response-schema.json bate com o retorno da API (requer dependência abaixo).

        <dependency>
            <groupId>com.jayway.restassured</groupId>
            <artifactId>json-schema-validator</artifactId>
            <version>2.5.0</version>
            <scope>test</scope>
        </dependency>

Bem, estes exemplos tentam mostrar como o REST-assured é uma excelente ferramenta para testes de APIs REST. Uma vez definidos os endpoints, os métodos, as entradas e saídas, é possível utilizá-la para garantir a qualidade da API. Os testes podem ser executados por linha de comando (mvn test) ou podem fazer parte de um pipeline de entrega contínua.

Espero que tenha gostado, e aguarde o próximo post 🙂

Google Maps Geocoding API

 

A plataforma do Post Denúncia usa a Google Maps Geocoding API para identificar a localização do denunciante. Quando as coordenadas (latitude e longitude) são recebidas, são submetidas à API. Ela então retorna várias informações, dentre elas o endereço, o bairro, a cidade e o estado onde o problema urbano foi reportado. Desta forma, a denúncia pode seguir para o órgão com toda a informação necessária para seu tratamento.

Para se comunicar com a API, foi desenvolvido um módulo cliente, apesar da própria equipe do Google Maps disponibilizar no GitHub uma implementação cliente em Java, aqui. Não tínhamos necessidade de algo complexo, enquanto a solução oficial contemplava não apenas a Geocoding API, mas outras APIs do Google Maps também. Construímos então uma biblioteca Java simples, que no momento deste post está na versão 1.2, e que pode ser obtida no GitHub da mesma forma, aqui.

No projeto google-geocode são duas as principais classes: GoogleGeocode e GeocodeResponse. A classe GoogleGeocode faz a comunicação com a Google Maps Geocoding API propriamente dita, passando como parâmetro as coordenadas geográficas ou um endereço específico, e convertendo a resposta em formato JSON para um objeto da classe GeocodeResponse (para realizar essa conversão, utilizamos o Jackson). Com a classe GeocodeResponse é possível conhecer o status da resposta e percorrer a lista com os resultados fornecidos pela API.

Como o Post Denúncia se restringe ao território brasileiro, as classes foram estendidas em BrazilGoogleGeocode e BrazilGeocodeResponse. Elas podem ser visualizadas aqui. Deste modo, pôde-se definir PT_br como idioma para o retorno dos resultados, e também implementar métodos específicos que retornassem adequadamente o nome do bairro e da cidade.

Tenha em mente que, ao usar o serviço de geolocalização do Google, você está criando uma dependência para sua aplicação, e precisa gerenciar isso. A Google garante disponibilidade para quem estiver em conformidade com os Termos de Serviço, que requer dentre outras coisas que sua aplicação seja pública e gratuita.

A Google define também limites de utilização. Os usuários da API padrão, não paga (nosso caso), podem fazer até 2.500 requisições por dia, e no máximo 10 solicitações por segundo. Isso é controlado através da chave da aplicação, que é passada como parâmetro na requisição. Apesar de ser uma recomendação da Google, na solução que demos não passamos a chave. O controle, nesse caso, é feito a partir do IP de onde as requisições estão sendo feitas. Se você for usar a nossa biblioteca google-geocode, tenha consciência disso. Caso queira colaborar com o projeto e implementar esta melhoria, basta fazer um pull request no repositório do Github.

Se não desejar o código do google-geocode, mas quiser usá-lo no seu projeto Maven, basta adicionar no pom.xml:

    <repositories>
        <repository>
            <id>esign-repo</id>
            <name>Esign Maven Repository</name>                                 
            <url>http://maven.esign.com.br</url>
        </repository>
    </repositories>

É necessário informar o repositório da Esign, para que o Maven saiba de onde baixar a dependência:

    <dependencies>
        <dependency>
            <groupId>br.com.esign</groupId>
            <artifactId>google-geocode</artifactId>
            <version>1.2.0</version>
        </dependency>
    </dependencies>

Fique à vontade para usar! 🙂

Edição feita em 20/11/2018: Desde 11 de Junho de 2018, a Google exige que sejam usadas API keys, conforme informado oficialmente. Desta forma, a biblioteca google-geocode foi atualizada, obrigando que uma chave de API fosse passada por parâmetro. Antes de usar a biblioteca, portanto, obtenha uma API key própria.

Com a alteração, foi lançada a versão 2.0 do google-geocode. Uma boa novidade é que a nova versão está disponibilizada no repositório central do Maven, não havendo mais a necessidade de adicionar o caminho para o repositório Maven da Esign no pom.xml, como orientado no post original. Basta adicionar a dependência:

    <dependencies>
        <dependency>
            <groupId>br.com.esign</groupId>
            <artifactId>google-geocode</artifactId>
            <version>2.0</version>
        </dependency>
    </dependencies>

Enjoy! 🙂