Publicação
Automatic tests generation for Restful Apis
| Resumo: | A programação de serviços web que fornecem interfaces aplicacionais que seguem os princípios do estilo arquitetural REST (Representational State Transfer) [38], designadas em inglês por RESTful APIs, e de aplicações cliente deste tipo de serviços é actualmente muito popular [63]. Por exemplo, aplicações como Twitter, Instagram, Youtube, Uber e Gitlab, fornecem acesso programático às suas aplicações cliente através deste tipo de APIs (Application Programming Interfaces). Isto acontece porque o uso deste tipo de APIs, quando comparado com as tradicionais interfaces de serviços web baseados em SOAP (Simple Object Access Protocol), simplificam grandemente o desenvolvimento das aplicações cliente. Mais recentemente, com o advento da arquitetura baseada em microserviços, o desenho de aplicações como conjuntos de serviços tornou-se muito comum, alavancando ainda mais a utilização das APIs REST [35]. O desenvolvimento eficaz de aplicações cliente deste tipo de serviços exige que as suas interfaces estejam bem documentadas. Apesar de iniciativas importantes como a Open API Specification [11], focadas na criação e promoção de um formato aberto para a descrição de APIs REST, o suporte à descrição deste tipo de APIs é atualmente extremamente limitado e incide, sobretudo, na estrutura e representações dos dados trocados entre clientes e fornecedores. De forma a ultrapassar as limitações existentes e suportar também a descrição de aspetos semânticos subjacentes às APIs REST, desenhámos e implementámos a linguagem HEADREST que permite especificar cada um dos seus serviços individualmente, num estilo reminescente dos triplos de Hoare [50], os quais designamos simplesmente por asserções e utilizando tipos de refinamento [45]. HEADREST é uma linguagem que inclui elementos para superar as limitações das abordagens existentes. O objetivo de HEADREST não é estender a Open API Specification, mas antes identificar primitivas que permitam aumentar o seu poder expressivo e demonstrar que é possível explorar estas descrições para avançar o estado da arte no que diz respeito à programação e testes de APIs REST. Normalmente, as regras de negócio de APIs REST esperam que os seus clientes enviem valores que respeitem alguma expressão lógica, por exemplo, um número de contribuinte. De forma a suportar esse requisito, HEADREST suporta tipos de refinamento que refinem um tipo base (booleano, inteiro, string ou array) perante uma fórmula. Em APIs REST, o estado da API consiste no conjunto de recursos que existem em algum instante temporal. Assim sendo, as asserções subdividem-se em um método (a ação a realizar), um URI (Uniform Resource Identifier) Template [46], uma pré-condição e uma pós-condição. Enquanto que a pré-condição especifica o estado no qual a asserção é válida, além de refinar os dados a enviar no pedido para a API, a pós-condição especifica o estado resultante da execução do pedido enviado e os dados de resposta produzidos pela API. Uma asserção descreve então que se um pedido para a execução de uma certa ação (por exemplo, POST [39]) sobre uma expansão do URI Template da asserção inclui dados que satisfazem a pré-condição, sendo que a ação é desenrolada num estado que satisfaz a pré-condição, então a resposta e o estado resultante satisfazem a pós-condição. De forma a descrever um estado esperado da API são usadas variáveis de recurso que representam recursos de um certo tipo. Usando estas variáveis de recurso e quantificadores existenciais ou universais é possível escrever as pré-condições ou pós-condições que descrevem o estado esperado. No geral, HEADREST suporta expressões lógicas, aritméticas e relacionais, predicados sobre arrays, além de acesso a propriedades de objetos bem como de entradas de arrays e, um predicado para verificar se uma dada string está no universo de uma expressão regular. Através do uso continuado de HEADREST, usando um estudo de caso desenvolvido para suportar o presente trabalho, foram adicionados construtores derivados da sintaxe base que reduzem a quantidade de código a escrever, bem como os potenciais erros subjacentes. De forma a facilitar a especificação de APIs REST em HEADREST desenvolvemos um plug-in para o Eclipse de modo a permitir a validação sintáctica e semântica. A implementação da linguagem foi feita utilizando a framework Xtext que permite o desenvolvimento de novas linguagens e plug-ins para o Eclipse. Podemos dividir a implementação em três partes: escrita da gramática, implementação de um mecanismo para verificar se todas variáveis de uma especificação estão devidamente declaradas e implementação do sistema de tipos. O nosso sistema de tipos é bidirecional, existindo duas relações de tipificação: uma de verificação de tipos; e outra de síntese de tipos. A verificação se um dado tipo é subtipo de outro tipo é feita de forma semântica [43] recorrendo a um SMT (Satisfiability Modulo Theories), nomeadamente Z3 [32]. Para tal baseamo-nos no trabalho de Bierman et al. [26], sendo que modificámos a axiomatização para o Z3 apresentada nesse trabalho de forma a contemplar os construtores da nossa linguagem. No futuro, espera-se conseguir gerar stubs servidor e SDKs (Software Development Kits) cliente a partir de especificações descritas usando HEADREST e verificar estaticamente código cliente e servidor de encontro a especificações HEADREST. Além disso, pretende-se integrar na linguagem questões de segurança em contexto REST, nomeadamente em termos de autenticação e de confidencialidade. Um dos usos possíveis desta linguagem é a geração e execução automática de testes. Para tal explorámos duas metodologias de testes diferentes. Ambas as metodologias apresentam como ponto comum a avaliação de uma asserção que é feita através do uso do Z3 para gerar pedidos que satisfaçam a pré-condição e inclui a verificação da pós-condição a partir da resposta obtida. A primeira metodologia envolve construir uma árvore de classificação [47] para cada asserção da especificação e usando um critério de cobertura, atualmente Minimum Coverage, gerar variações da pré-condição da asserção em questão. Estas variações exploram mudanças de certos elementos da linguagem (por exemplo, disjunções) tentando manter a satisfiabilidade da expressão. Por exemplo, uma disjunção e1 V e2 pode ser substituída por uma de três formas alternativas: e1 ^ e2, ¬e1 ^ e2 ou e1 ^ ¬e2. Esta metodologia exige que o testador especifique para cada caso de teste gerado o contexto no qual a nova asserção é satisfazível. Esse contexto é composto por uma sequência de outras asserções da especificação que são avaliadas antes de avaliar a própria asserção. A segunda metodologia tenta avaliar uma sequência aleatória de asserções de tamanho N. Para tal, a cada momento uma asserção é escolhida do conjunto de asserções da especificação. De forma a melhorar esta seleção pontuamos cada asserção é escolhida asserção que possua uma maior pontuação cuja pré-condição seja satisfazível. Repetimos este procedimento N vezes, podendo ainda repetir o algoritmo completo M vezes. A pontuação dada às asserções considera se uma dada asserção já foi avaliada alguma vez (Assertion Coverage), se um dado par de asserções, que finaliza na asserção em questão, já foi avaliado de forma consecutiva (Assertion Pair Coverage), o impacto que o método da asserção tem sobre a API (por exemplo, um POST bem sucedido tem maior impacto do que um POST mal sucedido). Em caso de empate, as asserções em questão são ordenadas de forma aleatória, sendo que é possível especificar a semente do gerador de números aleatórios de forma a que o teste de sequência seja determinista, podendo ser repetido mais tarde. Além disso, incluímos ainda um algoritmo adaptado do trabalho de Chakrabarti et al. [30] que verifica se uma dada API respeita a restrição do REST Hypermedia As The Engine of Application State. Este algoritmo pode ser executado após a avaliação de qualquer asserção, sendo que quando usado em conjunção com o teste de sequência permite identificar operações que fazem com que a API deixe de respeitar esse princípio. Da avaliação à primeira metodologia concluiu-se que esta tem o potencial de produzir um número elevado de casos de testes, apesar de que o testador tem de indicar para cada um desses casos de teste uma lista de asserções que devem ser avaliadas antes de avaliar o caso de teste propriamente dito. Da metodologia de teste da sequência aleatória de asserções concluiu-se que o uso de uma função que pontua asserções a cada instante da sequência conduz sempre a melhores resultados, podendo revelar até 101% mais cobertura ao nível de asserções ou pares de asserções, do que se for apenas usada uma ordenação aleatória das asserções. Além disso, através da função de pontuação obtivemos para o estudo de caso 99.27% de cobertura de pares de asserções enquanto que para as mesmas condições, a versão sem a função de pontuação apenas obteve 56.16%. |
|---|---|
| Autores principais: | Ferreira, Fábio Alexandre Canada |
| Assunto: | REST APIs REST SMT Tipos de refinamento Teste de sequência Teses de mestrado - 2017 |
| Ano: | 2017 |
| País: | Portugal |
| Tipo de documento: | dissertação de mestrado |
| Tipo de acesso: | acesso aberto |
| Instituição associada: | Universidade de Lisboa |
| Idioma: | inglês |
| Origem: | Repositório da Universidade de Lisboa |
| Resumo: | A programação de serviços web que fornecem interfaces aplicacionais que seguem os princípios do estilo arquitetural REST (Representational State Transfer) [38], designadas em inglês por RESTful APIs, e de aplicações cliente deste tipo de serviços é actualmente muito popular [63]. Por exemplo, aplicações como Twitter, Instagram, Youtube, Uber e Gitlab, fornecem acesso programático às suas aplicações cliente através deste tipo de APIs (Application Programming Interfaces). Isto acontece porque o uso deste tipo de APIs, quando comparado com as tradicionais interfaces de serviços web baseados em SOAP (Simple Object Access Protocol), simplificam grandemente o desenvolvimento das aplicações cliente. Mais recentemente, com o advento da arquitetura baseada em microserviços, o desenho de aplicações como conjuntos de serviços tornou-se muito comum, alavancando ainda mais a utilização das APIs REST [35]. O desenvolvimento eficaz de aplicações cliente deste tipo de serviços exige que as suas interfaces estejam bem documentadas. Apesar de iniciativas importantes como a Open API Specification [11], focadas na criação e promoção de um formato aberto para a descrição de APIs REST, o suporte à descrição deste tipo de APIs é atualmente extremamente limitado e incide, sobretudo, na estrutura e representações dos dados trocados entre clientes e fornecedores. De forma a ultrapassar as limitações existentes e suportar também a descrição de aspetos semânticos subjacentes às APIs REST, desenhámos e implementámos a linguagem HEADREST que permite especificar cada um dos seus serviços individualmente, num estilo reminescente dos triplos de Hoare [50], os quais designamos simplesmente por asserções e utilizando tipos de refinamento [45]. HEADREST é uma linguagem que inclui elementos para superar as limitações das abordagens existentes. O objetivo de HEADREST não é estender a Open API Specification, mas antes identificar primitivas que permitam aumentar o seu poder expressivo e demonstrar que é possível explorar estas descrições para avançar o estado da arte no que diz respeito à programação e testes de APIs REST. Normalmente, as regras de negócio de APIs REST esperam que os seus clientes enviem valores que respeitem alguma expressão lógica, por exemplo, um número de contribuinte. De forma a suportar esse requisito, HEADREST suporta tipos de refinamento que refinem um tipo base (booleano, inteiro, string ou array) perante uma fórmula. Em APIs REST, o estado da API consiste no conjunto de recursos que existem em algum instante temporal. Assim sendo, as asserções subdividem-se em um método (a ação a realizar), um URI (Uniform Resource Identifier) Template [46], uma pré-condição e uma pós-condição. Enquanto que a pré-condição especifica o estado no qual a asserção é válida, além de refinar os dados a enviar no pedido para a API, a pós-condição especifica o estado resultante da execução do pedido enviado e os dados de resposta produzidos pela API. Uma asserção descreve então que se um pedido para a execução de uma certa ação (por exemplo, POST [39]) sobre uma expansão do URI Template da asserção inclui dados que satisfazem a pré-condição, sendo que a ação é desenrolada num estado que satisfaz a pré-condição, então a resposta e o estado resultante satisfazem a pós-condição. De forma a descrever um estado esperado da API são usadas variáveis de recurso que representam recursos de um certo tipo. Usando estas variáveis de recurso e quantificadores existenciais ou universais é possível escrever as pré-condições ou pós-condições que descrevem o estado esperado. No geral, HEADREST suporta expressões lógicas, aritméticas e relacionais, predicados sobre arrays, além de acesso a propriedades de objetos bem como de entradas de arrays e, um predicado para verificar se uma dada string está no universo de uma expressão regular. Através do uso continuado de HEADREST, usando um estudo de caso desenvolvido para suportar o presente trabalho, foram adicionados construtores derivados da sintaxe base que reduzem a quantidade de código a escrever, bem como os potenciais erros subjacentes. De forma a facilitar a especificação de APIs REST em HEADREST desenvolvemos um plug-in para o Eclipse de modo a permitir a validação sintáctica e semântica. A implementação da linguagem foi feita utilizando a framework Xtext que permite o desenvolvimento de novas linguagens e plug-ins para o Eclipse. Podemos dividir a implementação em três partes: escrita da gramática, implementação de um mecanismo para verificar se todas variáveis de uma especificação estão devidamente declaradas e implementação do sistema de tipos. O nosso sistema de tipos é bidirecional, existindo duas relações de tipificação: uma de verificação de tipos; e outra de síntese de tipos. A verificação se um dado tipo é subtipo de outro tipo é feita de forma semântica [43] recorrendo a um SMT (Satisfiability Modulo Theories), nomeadamente Z3 [32]. Para tal baseamo-nos no trabalho de Bierman et al. [26], sendo que modificámos a axiomatização para o Z3 apresentada nesse trabalho de forma a contemplar os construtores da nossa linguagem. No futuro, espera-se conseguir gerar stubs servidor e SDKs (Software Development Kits) cliente a partir de especificações descritas usando HEADREST e verificar estaticamente código cliente e servidor de encontro a especificações HEADREST. Além disso, pretende-se integrar na linguagem questões de segurança em contexto REST, nomeadamente em termos de autenticação e de confidencialidade. Um dos usos possíveis desta linguagem é a geração e execução automática de testes. Para tal explorámos duas metodologias de testes diferentes. Ambas as metodologias apresentam como ponto comum a avaliação de uma asserção que é feita através do uso do Z3 para gerar pedidos que satisfaçam a pré-condição e inclui a verificação da pós-condição a partir da resposta obtida. A primeira metodologia envolve construir uma árvore de classificação [47] para cada asserção da especificação e usando um critério de cobertura, atualmente Minimum Coverage, gerar variações da pré-condição da asserção em questão. Estas variações exploram mudanças de certos elementos da linguagem (por exemplo, disjunções) tentando manter a satisfiabilidade da expressão. Por exemplo, uma disjunção e1 V e2 pode ser substituída por uma de três formas alternativas: e1 ^ e2, ¬e1 ^ e2 ou e1 ^ ¬e2. Esta metodologia exige que o testador especifique para cada caso de teste gerado o contexto no qual a nova asserção é satisfazível. Esse contexto é composto por uma sequência de outras asserções da especificação que são avaliadas antes de avaliar a própria asserção. A segunda metodologia tenta avaliar uma sequência aleatória de asserções de tamanho N. Para tal, a cada momento uma asserção é escolhida do conjunto de asserções da especificação. De forma a melhorar esta seleção pontuamos cada asserção é escolhida asserção que possua uma maior pontuação cuja pré-condição seja satisfazível. Repetimos este procedimento N vezes, podendo ainda repetir o algoritmo completo M vezes. A pontuação dada às asserções considera se uma dada asserção já foi avaliada alguma vez (Assertion Coverage), se um dado par de asserções, que finaliza na asserção em questão, já foi avaliado de forma consecutiva (Assertion Pair Coverage), o impacto que o método da asserção tem sobre a API (por exemplo, um POST bem sucedido tem maior impacto do que um POST mal sucedido). Em caso de empate, as asserções em questão são ordenadas de forma aleatória, sendo que é possível especificar a semente do gerador de números aleatórios de forma a que o teste de sequência seja determinista, podendo ser repetido mais tarde. Além disso, incluímos ainda um algoritmo adaptado do trabalho de Chakrabarti et al. [30] que verifica se uma dada API respeita a restrição do REST Hypermedia As The Engine of Application State. Este algoritmo pode ser executado após a avaliação de qualquer asserção, sendo que quando usado em conjunção com o teste de sequência permite identificar operações que fazem com que a API deixe de respeitar esse princípio. Da avaliação à primeira metodologia concluiu-se que esta tem o potencial de produzir um número elevado de casos de testes, apesar de que o testador tem de indicar para cada um desses casos de teste uma lista de asserções que devem ser avaliadas antes de avaliar o caso de teste propriamente dito. Da metodologia de teste da sequência aleatória de asserções concluiu-se que o uso de uma função que pontua asserções a cada instante da sequência conduz sempre a melhores resultados, podendo revelar até 101% mais cobertura ao nível de asserções ou pares de asserções, do que se for apenas usada uma ordenação aleatória das asserções. Além disso, através da função de pontuação obtivemos para o estudo de caso 99.27% de cobertura de pares de asserções enquanto que para as mesmas condições, a versão sem a função de pontuação apenas obteve 56.16%. |
|---|