Automação e virtualização de serviços

Elias Nogueira
Elias NogueiraLead Software Engineer em Waes
Automação e Virtualização de
serviços REST com
RestAssured + Wiremock + Docker
@eliasnogueira
Passos...
1 Entender a documentação da API
2 Pensar nos testes com uma divisão de pipeline
3 Criar uma versão inicial da arquitetura
4 Criar os testes e definir as suítes de testes
5 Criar mocks para resolver problemas
* massa de dados, dependências, indisponibilidades
API do TDC
https://www.globalcode.com.br/api
Expõe a API de consulta a:
§ Eventos
§ Modalidades
§ Coordenadores
§ Palestrantes
§ Palestras
Através de um ClientID e um
Secret é gerado um
Access-Token em OAuth2
Possui a documentação via
OpenAPI para download na
própria página de acesso
1.
Entender a
documentação da API
Entender os controllers
§ operações
§ endpoints
§ parâmetros de entrada
§ saída
§ autenticação
2.
Pensar nos testes com
uma divisão de
pipeline
API
Pipeline
Health Check
Contrato
Funcional
Aceitação
Garantir que o endpoint está respondendo
HTTP 200
Garantir que o endpoint não teve seus
atributos alterados
Garantir que o endpoint funciona ou apresenta
os resultados de falha esperados
Garantir que um conjunto de endpoints
funcionam como na UI
3.
Criar uma versão
inicial da arquitetura
Arquitetura
src test
Geração do Access Token
necessário para a execução
dos testes
Leitor de Configurações
para ler, ao menos, os
dados de autenticação
beans e builders
suportar melhor a escrita
dos testes
BaseTest
para remover a duplicidade
de pré-condição
json schemas
schemas dos recursos para
o teste de contrato
Suítes de Teste
habilitará a rápida
execução de testes
4.
Criar testes e definir as
suítes de teste
Rest-Assured
http://rest-assured.io
DSL Java para testar e validar APIs REST.
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;
public class ExemploRestAssured {
@Test
public void boasVindas() {
given().
param("nome", "Elias").
when().
post("/cadastro").
then().
body("mensagem", is("Olá Elias"));
}
}
Rest-Assured
http://rest-assured.io
DSL Java para testar e validar APIs REST.
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;
public class ExemploRestAssured {
@Test
public void boasVindas() {
given().
param("nome", "Elias").
when().
post("/cadastro").
then().
body("mensagem", is("Olá Elias"));
}
}
importação das
bibliotecas necessárias
Rest-Assured
http://rest-assured.io
DSL Java para testar e validar APIs REST.
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;
public class ExemploRestAssured {
@Test
public void boasVindas() {
given().
param("nome", "Elias").
when().
post("/cadastro").
then().
body("mensagem", is("Olá Elias"));
}
}
pré-condição para a
requisição
Rest-Assured
http://rest-assured.io
DSL Java para testar e validar APIs REST.
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;
public class ExemploRestAssured {
@Test
public void boasVindas() {
given().
param("nome", "Elias").
when().
post("/cadastro").
then().
body("mensagem", is("Olá Elias"));
}
}
ação (requisição)
Rest-Assured
http://rest-assured.io
DSL Java para testar e validar APIs REST.
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;
public class ExemploRestAssured {
@Test
public void boasVindas() {
given().
param("nome", "Elias").
when().
post("/cadastro").
then().
body("mensagem", is("Olá Elias"));
}
}
validação dos dados
de retorno
Gerar do Access-Token
public String getAccessToken() {
Configuration configuration = new Configuration();
String username = configuration.getAuthenticationUsername();
String password = configuration.getAuthenticationPassword();
baseURI = configuration.getAPIURL();
basePath = configuration.getAPIOauthContext();
return
given().
auth()
.preemptive().basic(username, password).
when().
get("/").
then().
extract().
path("Access-Token");
}
Gerar do Access-Token
public String getAccessToken() {
Configuration configuration = new Configuration();
String username = configuration.getAuthenticationUsername();
String password = configuration.getAuthenticationPassword();
baseURI = configuration.getAPIURL();
basePath = configuration.getAPIOauthContext();
return
given().
auth()
.preemptive().basic(username, password).
when().
get("/").
then().
extract().
path("Access-Token");
}
a autenticação é enviada
como pré-condição
Gerar do Access-Token
public String getAccessToken() {
Configuration configuration = new Configuration();
String username = configuration.getAuthenticationUsername();
String password = configuration.getAuthenticationPassword();
baseURI = configuration.getAPIURL();
basePath = configuration.getAPIOauthContext();
return
given().
auth()
.preemptive().basic(username, password).
when().
get("/").
then().
extract().
path("Access-Token");
}
access-token é
retornado como
String
Leitor de Configurações
public String getAccessToken() {
Configuration configuration = new Configuration();
String username = configuration.getAuthenticationUsername();
String password = configuration.getAuthenticationPassword();
baseURI = configuration.getAPIURL();
basePath = configuration.getAPIOauthContext();
return
given().
auth()
.preemptive().basic(username, password).
when().
get("/").
then().
extract().
path("Access-Token");
}
lê dados de
autenticação
Leitor de Configurações
public String getAccessToken() {
Configuration configuration = new Configuration();
String username = configuration.getAuthenticationUsername();
String password = configuration.getAuthenticationPassword();
baseURI = configuration.getAPIURL();
basePath = configuration.getAPIOauthContext();
return
given().
auth()
.preemptive().basic(username, password).
when().
get("/").
then().
extract().
path("Access-Token");
}
lê dados para
acesso ao token
BaseTest
public abstract class BaseTest {
protected static String accessToken;
@BeforeClass(alwaysRun = true)
public static void beforeClass() {
Configuration configuration = new Configuration();
accessToken = new GenerateAccessToken().
getAccessToken();
baseURI = configuration.getAPIURL();
basePath = configuration.getPublico();
}
}
Código executado antes de cada teste
BaseTest
public abstract class BaseTest {
protected static String accessToken;
@BeforeClass(alwaysRun = true)
public static void beforeClass() {
Configuration configuration = new Configuration();
accessToken = new GenerateAccessToken().
getAccessToken();
baseURI = configuration.getAPIURL();
basePath = configuration.getPublico();
}
}
Código executado antes de cada teste
accessToken que
será acessado pelas
classes de teste
BaseTest
public abstract class BaseTest {
protected static String accessToken;
@BeforeClass(alwaysRun = true)
public static void beforeClass() {
Configuration configuration = new Configuration();
accessToken = new GenerateAccessToken().
getAccessToken();
baseURI = configuration.getAPIURL();
basePath = configuration.getPublico();
}
}
Código executado antes de cada teste
gera o accessToken
apenas uma vez para
a execução de teste
BaseTest
public abstract class BaseTest {
protected static String accessToken;
@BeforeClass(alwaysRun = true)
public static void beforeClass() {
Configuration configuration = new Configuration();
accessToken = new GenerateAccessToken().
getAccessToken();
baseURI = configuration.getAPIURL();
basePath = configuration.getPublico();
}
}
Código executado antes de cada teste
chamada da classe
de geração do token
BaseTest
public abstract class BaseTest {
protected static String accessToken;
@BeforeClass(alwaysRun = true)
public static void beforeClass() {
Configuration configuration = new Configuration();
accessToken = new GenerateAccessToken().
getAccessToken();
baseURI = configuration.getAPIURL();
basePath = configuration.getPublico();
}
}
Código executado antes de cada teste
necessário informar
novamente o
apontamento da API
* porque já foi apontado na geração do access token
health-check
Garantir que o endpoint está
respondendo
heath-check
Apenas validamos se o statuscode é 200
public class EventoDetalheTest extends BaseTest {
@Test(groups = { "health" })
public void healthCheck() {
given().
auth().oauth2(accessToken).
when().
get("evento/{id}", id).
then().
statusCode(HttpStatus.SC_OK);
}
heath-check
Apenas validamos se o statuscode é 200
tag de grupo para
filtrar a execução
public class EventoDetalheTest extends BaseTest {
@Test(groups = { "health" })
public void healthCheck() {
given().
auth().oauth2(accessToken).
when().
get("evento/{id}", id).
then().
statusCode(HttpStatus.SC_OK);
}
heath-check
Apenas validamos se o statuscode é 200
public class EventoDetalheTest extends BaseTest {
@Test(groups = { "health" })
public void healthCheck() {
given().
auth().oauth2(accessToken).
when().
get("evento/{id}", id).
then().
statusCode(HttpStatus.SC_OK);
}
inserir sempre o access-token
como pré-condição da requisição
heath-check
Apenas validamos se o statuscode é 200
public class EventoDetalheTest extends BaseTest {
@Test(groups = { "health" })
public void healthCheck() {
given().
auth().oauth2(accessToken).
when().
get("evento/{id}", id).
then().
statusCode(HttpStatus.SC_OK);
}
validar se o status code,
e somente ele, é 200
contrato
Garantir que o endpoint não teve
seus atributos alterados
● É o nome dado ao pacto entre o produtor e consumidor
● Garante que mudanças na API não invalidem o consumo:
● path
● parâmetros
● dados de envio (request)
● dados de retorno (response body)
● json-schema é um contrato que define os dados
esperados, tipos e formatos de cada campo na resposta
contrato
{
"nome": "Elias",
"idade": 36
}
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"nome": {
"type": "string"
},
"idade": {
"type": "integer"
}
},
"required": [
"nome",
"idade"
],
"additionalProperties": false
}
json-schema
{
"nome": "Elias",
"idade": 36
}
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"nome": {
"type": "string"
},
"idade": {
"type": "integer"
}
},
"required": [
"nome",
"idade"
],
"additionalProperties": false
}
json-schema possui o nome
do atributo e o tipo de dados
json-schema
{
"nome": "Elias",
"idade": 36
}
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"nome": {
"type": "string"
},
"idade": {
"type": "integer"
}
},
"required": [
"nome",
"idade"
],
"additionalProperties": false
}
os dois atributos devem estar
presentes, obrigatoriamente
json-schema
{
"nome": "Elias",
"idade": 36
}
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"nome": {
"type": "string"
},
"idade": {
"type": "integer"
}
},
"required": [
"nome",
"idade"
],
"additionalProperties": false
}
json-schema
nenhum outro
atributo é permitido
teste funcional
na minha máquina funciona
¯_(ツ)_/¯
funcional
Validar cenários positivos e negativos (caminho feliz | fluxo exceção)
@Test(groups = {"funcional"})
public void validarEventoPeloID() {
given().
auth().oauth2(accessToken).
when().
get("evento/{id}", 110).
then().
statusCode(HttpStatus.SC_OK).
body("[0].id", is(110)).
body("[0].descricao", is("TDC 2019 Florianópolis")).
body("[0].chave", is("tdc-2019-florianopolis")).
body("[0].ativo", is(true)).
body("[0].dias", is(5)).
body("[0].dataInicio", is("2019-04-23 00:00:00")).
body("[0].dataTermino", is("2019-04-27 00:00:00"));
}
funcional
Validar cenários positivos e negativos (caminho feliz | fluxo exceção)
@Test(groups = {"funcional"})
public void validarEventoPeloID() {
given().
auth().oauth2(accessToken).
when().
get("evento/{id}", 110).
then().
statusCode(HttpStatus.SC_OK).
body("[0].id", is(110)).
body("[0].descricao", is("TDC 2019 Florianópolis")).
body("[0].chave", is("tdc-2019-florianopolis")).
body("[0].ativo", is(true)).
body("[0].dias", is(5)).
body("[0].dataInicio", is("2019-04-23 00:00:00")).
body("[0].dataTermino", is("2019-04-27 00:00:00"));
}
tag de grupo para
filtrar a execução
funcional
Validar cenários positivos e negativos (caminho feliz | fluxo exceção)
@Test(groups = {"funcional"})
public void validarEventoPeloID() {
given().
auth().oauth2(accessToken).
when().
get("evento/{id}", 110).
then().
statusCode(HttpStatus.SC_OK).
body("[0].id", is(110)).
body("[0].descricao", is("TDC 2019 Florianópolis")).
body("[0].chave", is("tdc-2019-florianopolis")).
body("[0].ativo", is(true)).
body("[0].dias", is(5)).
body("[0].dataInicio", is("2019-04-23 00:00:00")).
body("[0].dataTermino", is("2019-04-27 00:00:00"));
}
requisição para obter
o detalhe de um
determinado evento
funcional
Validar cenários positivos e negativos (caminho feliz | fluxo exceção)
@Test(groups = {"funcional"})
public void validarEventoPeloID() {
given().
auth().oauth2(accessToken).
when().
get("evento/{id}", 110).
then().
statusCode(HttpStatus.SC_OK).
body("[0].id", is(110)).
body("[0].descricao", is("TDC 2019 Florianópolis")).
body("[0].chave", is("tdc-2019-florianopolis")).
body("[0].ativo", is(true)).
body("[0].dias", is(5)).
body("[0].dataInicio", is("2019-04-23 00:00:00")).
body("[0].dataTermino", is("2019-04-27 00:00:00"));
}
validação dos dados
de retorno (body)
* [0] existe ali porque o retorno é um array de uma posição
* é um possível bug da implementação
aceitação
Garantir que um conjunto de endpoints
funcionam como na UI
Testar com a perspectiva do usuário
● Entrar no evento The Developers Conference Florianópolis
● Clicar na Trilha DevTest
● Visualizar os detalhes da palestra Automação e
Virtualização de serviços REST com RestAssured +
Wiremock + Docker
aceitação
@Test(groups = {"aceitacao"})
public void visualizaDetalhes_EncontrandoDados() throws ObjetoNotFoundException {
String tituloPalestra = "Automação e Virtualização de serviços REST ";
Evento evento = encontraEvento("TDC 2019 Florianópolis");
Modalidade trilha = encontraTrilha(evento, "Trilha DevTest");
Palestra palestra = encontraPalestra(evento, trilha, tituloPalestra);
given().
auth().
oauth2(accessToken).
pathParam("eventoID", evento.getId()).
pathParam("modalidadeID", trilha.getId()).
pathParam("palestraID", palestra.getId()).
when().
get("/evento/{eventoID}/modalidade/{modalidadeID}/palestra/{palestraID}/palestrantes").
then().
statusCode(200).
body("[0].member.nome", is("Elias Nogueira")).
body("[0].member.empresa", is("Sicredi"));
}
aceitação
@Test(groups = {"aceitacao"})
public void visualizaDetalhes_EncontrandoDados() throws ObjetoNotFoundException {
String tituloPalestra = "Automação e Virtualização de serviços REST ";
Evento evento = encontraEvento("TDC 2019 Florianópolis");
Modalidade trilha = encontraTrilha(evento, "Trilha DevTest");
Palestra palestra = encontraPalestra(evento, trilha, tituloPalestra);
given().
auth().
oauth2(accessToken).
pathParam("eventoID", evento.getId()).
pathParam("modalidadeID", trilha.getId()).
pathParam("palestraID", palestra.getId()).
when().
get("/evento/{eventoID}/modalidade/{modalidadeID}/palestra/{palestraID}/palestrantes").
then().
statusCode(200).
body("[0].member.nome", is("Elias Nogueira")).
body("[0].member.empresa", is("Sicredi"));
}
funções auxiliares para chamadas a
cada ação do usuário
aceitação
@Test(groups = {"aceitacao"})
public void visualizaDetalhes_EncontrandoDados() throws ObjetoNotFoundException {
String tituloPalestra = "Automação e Virtualização de serviços REST ";
Evento evento = encontraEvento("TDC 2019 Florianópolis");
Modalidade trilha = encontraTrilha(evento, "Trilha DevTest");
Palestra palestra = encontraPalestra(evento, trilha, tituloPalestra);
given().
auth().
oauth2(accessToken).
pathParam("eventoID", evento.getId()).
pathParam("modalidadeID", trilha.getId()).
pathParam("palestraID", palestra.getId()).
when().
get("/evento/{eventoID}/modalidade/{modalidadeID}/palestra/{palestraID}/palestrantes").
then().
statusCode(200).
body("[0].member.nome", is("Elias Nogueira")).
body("[0].member.empresa", is("Sicredi"));
}
uso dos dados obtidos
para a requisição
service virtualization
disponibilizar mocks de seviços
● Forma de simular comportamentos de componentes
baseados em API
● Habilita o uso de serviços virtuais ao invés de serviços de
produção mesmo que componentes chave não estejam
disponíveis
service virtualization
service virtualization
{
"id" : "f238845e-369c-4a34-aa69-04d9240d7fb5",
"name" : "evento_modalidade_palestra",
"request" : {
"urlPattern" : "/v1/publico/evento/110/modalidade/2685/palestra/10083",
"method" : "GET"
},
"response" : {
"status" : 200,
"bodyFileName" : "evento_modalidade_palestra_data.json",
"headers" : {
"Authorization": "*"
}
},
"persistent" : true,
"insertionIndex" : 1
}
service virtualization
{
"id" : "f238845e-369c-4a34-aa69-04d9240d7fb5",
"name" : "evento_modalidade_palestra",
"request" : {
"urlPattern" : "/v1/publico/evento/110/modalidade/2685/palestra/10083",
"method" : "GET"
},
"response" : {
"status" : 200,
"bodyFileName" : "evento_modalidade_palestra_data.json",
"headers" : {
"Authorization": "*"
}
},
"persistent" : true,
"insertionIndex" : 1
}
sempre que uma requisição for igual a esta
URL (inclusive com os dados)
service virtualization
{
"id" : "f238845e-369c-4a34-aa69-04d9240d7fb5",
"name" : "evento_modalidade_palestra",
"request" : {
"urlPattern" : "/v1/publico/evento/110/modalidade/2685/palestra/10083",
"method" : "GET"
},
"response" : {
"status" : 200,
"bodyFileName" : "evento_modalidade_palestra_data.json",
},
"persistent" : true,
"insertionIndex" : 1
}
a api responderá os dados abaixo...
service virtualization
{
"id": 10083,
"slot": 6,
"ordem": 1,
"titulo": "Automação e Virtualização de serviços REST...",
"descricao": ”Você sempre quis aprender a como testar...",
"tipo": 2,
"horario": "16:40"
}
dados de retorno
@eliasnogueira
github.com/eliasnogueira
Obrigado!
1 de 53

Mais conteúdo relacionado

Mais procurados

API Test Automation API Test Automation
API Test Automation SQALab
1.9K visualizações26 slides
Api testingApi testing
Api testingHamzaMajid13
825 visualizações19 slides
Testes E2E em Cypress com JSTestes E2E em Cypress com JS
Testes E2E em Cypress com JSNàtali Cabral
236 visualizações22 slides
Belajar Postman test runnerBelajar Postman test runner
Belajar Postman test runnerFachrul Choliluddin
1.3K visualizações18 slides

Mais procurados(20)

API Test Automation API Test Automation
API Test Automation
SQALab1.9K visualizações
Api testingApi testing
Api testing
HamzaMajid13825 visualizações
Testes E2E em Cypress com JSTestes E2E em Cypress com JS
Testes E2E em Cypress com JS
Nàtali Cabral236 visualizações
Belajar Postman test runnerBelajar Postman test runner
Belajar Postman test runner
Fachrul Choliluddin1.3K visualizações
API Testing. Streamline your testing process.API Testing. Streamline your testing process.
API Testing. Streamline your testing process.
Andrey Oleynik792 visualizações
An introduction to api testing | David TzemachAn introduction to api testing | David Tzemach
An introduction to api testing | David Tzemach
David Tzemach1.5K visualizações
Arquitetura básica de testes para seu projeto JavaArquitetura básica de testes para seu projeto Java
Arquitetura básica de testes para seu projeto Java
Elias Nogueira2.1K visualizações
API TestingAPI Testing
API Testing
Bikash Sharma7.8K visualizações
Api TestingApi Testing
Api Testing
Vishwanath KC1.4K visualizações
Api testingApi testing
Api testing
Keshav Kashyap30.1K visualizações
API Test Automation Using Karate (Anil Kumar Moka)API Test Automation Using Karate (Anil Kumar Moka)
API Test Automation Using Karate (Anil Kumar Moka)
Peter Thomas5.3K visualizações
Karate - Web-Service API Testing Made SimpleKarate - Web-Service API Testing Made Simple
Karate - Web-Service API Testing Made Simple
VodqaBLR3.2K visualizações
Saving Time By Testing With JestSaving Time By Testing With Jest
Saving Time By Testing With Jest
Ben McCormick2.9K visualizações
Test Design and Automation for REST APITest Design and Automation for REST API
Test Design and Automation for REST API
Ivan Katunou882 visualizações
2015-StarWest presentation on REST-assured2015-StarWest presentation on REST-assured
2015-StarWest presentation on REST-assured
Eing Ong3K visualizações
PostmanPostman
Postman
Igor Shubovych7.5K visualizações

Similar a Automação e virtualização de serviços

Play Framework - FLISOLPlay Framework - FLISOL
Play Framework - FLISOLgrupoweblovers
1K visualizações46 slides
VraptorVraptor
Vraptorclauvane1708
373 visualizações20 slides

Similar a Automação e virtualização de serviços(20)

TDD - Algumas lições aprendidas com o livro GOOSTDD - Algumas lições aprendidas com o livro GOOS
TDD - Algumas lições aprendidas com o livro GOOS
Fábio Miranda786 visualizações
Play Framework - FLISOLPlay Framework - FLISOL
Play Framework - FLISOL
grupoweblovers1K visualizações
VraptorVraptor
Vraptor
clauvane1708373 visualizações
Workshop Microservices - Construindo APIs RESTful com Spring BootWorkshop Microservices - Construindo APIs RESTful com Spring Boot
Workshop Microservices - Construindo APIs RESTful com Spring Boot
Rodrigo Cândido da Silva3.8K visualizações
PDC - Engenharia - Plataforma Microsoft .NETPDC - Engenharia - Plataforma Microsoft .NET
PDC - Engenharia - Plataforma Microsoft .NET
slides_teltools567 visualizações
Ecosistema spring a_plataforma_enterprise_javEcosistema spring a_plataforma_enterprise_jav
Ecosistema spring a_plataforma_enterprise_jav
Julio Viegas1.2K visualizações
CDI Extensions e DeltaSpikeCDI Extensions e DeltaSpike
CDI Extensions e DeltaSpike
Rafael Benevides1.3K visualizações
VRaptor4VRaptor4
VRaptor4
Renan Montenegro818 visualizações
Spring Capitulo 03Spring Capitulo 03
Spring Capitulo 03
Diego Pacheco3.8K visualizações
JavaOne LATAM 2016 - Combinando AngularJS com Java EEJavaOne LATAM 2016 - Combinando AngularJS com Java EE
JavaOne LATAM 2016 - Combinando AngularJS com Java EE
Rodrigo Cândido da Silva941 visualizações
Workshop05Workshop05
Workshop05
Philippe Guillaume, MBA,CISSP, COBIT441 visualizações
API ApontadorAPI Apontador
API Apontador
Leonardo Andreucci571 visualizações
Design patternsDesign patterns
Design patterns
DouglasSoaresAndrSch42 visualizações
Como conectar programas em linguagem java  a bases de dadosComo conectar programas em linguagem java  a bases de dados
Como conectar programas em linguagem java a bases de dados
Henrique Fernandes230 visualizações
Wicket 2008Wicket 2008
Wicket 2008
Claudio Miranda933 visualizações
Teste de Integracao com DbUnit e JStrykerTeste de Integracao com DbUnit e JStryker
Teste de Integracao com DbUnit e JStryker
Washington Botelho1K visualizações
Automação de Teste para REST, Web e MobileAutomação de Teste para REST, Web e Mobile
Automação de Teste para REST, Web e Mobile
Elias Nogueira3.6K visualizações
ASP.NET Web APIASP.NET Web API
ASP.NET Web API
Waldyr Felix1.3K visualizações
Spring MVC - QConSPSpring MVC - QConSP
Spring MVC - QConSP
Eduardo Bregaida4.3K visualizações

Mais de Elias Nogueira(20)

O Agile Coach pode (e muitas vezes deve) ser técnicoO Agile Coach pode (e muitas vezes deve) ser técnico
O Agile Coach pode (e muitas vezes deve) ser técnico
Elias Nogueira714 visualizações
Create an architecture for web test automationCreate an architecture for web test automation
Create an architecture for web test automation
Elias Nogueira1.2K visualizações
Papel do QA na Transformação ÁgilPapel do QA na Transformação Ágil
Papel do QA na Transformação Ágil
Elias Nogueira1.2K visualizações
BDD não é automação de teste - Scrum GatheringBDD não é automação de teste - Scrum Gathering
BDD não é automação de teste - Scrum Gathering
Elias Nogueira4.1K visualizações
BDD não é Automação de TestesBDD não é Automação de Testes
BDD não é Automação de Testes
Elias Nogueira5.3K visualizações
Como ter sucesso ministrando uma palestra técnicaComo ter sucesso ministrando uma palestra técnica
Como ter sucesso ministrando uma palestra técnica
Elias Nogueira594 visualizações
Tem que testar mesmo?Tem que testar mesmo?
Tem que testar mesmo?
Elias Nogueira604 visualizações
Testes em todos os niveis de planejamentoTestes em todos os niveis de planejamento
Testes em todos os niveis de planejamento
Elias Nogueira643 visualizações
Coaching the Agile CoachCoaching the Agile Coach
Coaching the Agile Coach
Elias Nogueira1.2K visualizações
Java Test Automation for REST, Web and MobileJava Test Automation for REST, Web and Mobile
Java Test Automation for REST, Web and Mobile
Elias Nogueira1.6K visualizações
O que é um Agile CoachO que é um Agile Coach
O que é um Agile Coach
Elias Nogueira1.8K visualizações

Automação e virtualização de serviços

  • 1. Automação e Virtualização de serviços REST com RestAssured + Wiremock + Docker @eliasnogueira
  • 2. Passos... 1 Entender a documentação da API 2 Pensar nos testes com uma divisão de pipeline 3 Criar uma versão inicial da arquitetura 4 Criar os testes e definir as suítes de testes 5 Criar mocks para resolver problemas * massa de dados, dependências, indisponibilidades
  • 3. API do TDC https://www.globalcode.com.br/api Expõe a API de consulta a: § Eventos § Modalidades § Coordenadores § Palestrantes § Palestras Através de um ClientID e um Secret é gerado um Access-Token em OAuth2 Possui a documentação via OpenAPI para download na própria página de acesso
  • 5. Entender os controllers § operações § endpoints § parâmetros de entrada § saída § autenticação
  • 6. 2. Pensar nos testes com uma divisão de pipeline
  • 7. API Pipeline Health Check Contrato Funcional Aceitação Garantir que o endpoint está respondendo HTTP 200 Garantir que o endpoint não teve seus atributos alterados Garantir que o endpoint funciona ou apresenta os resultados de falha esperados Garantir que um conjunto de endpoints funcionam como na UI
  • 9. Arquitetura src test Geração do Access Token necessário para a execução dos testes Leitor de Configurações para ler, ao menos, os dados de autenticação beans e builders suportar melhor a escrita dos testes BaseTest para remover a duplicidade de pré-condição json schemas schemas dos recursos para o teste de contrato Suítes de Teste habilitará a rápida execução de testes
  • 10. 4. Criar testes e definir as suítes de teste
  • 11. Rest-Assured http://rest-assured.io DSL Java para testar e validar APIs REST. import static io.restassured.RestAssured.*; import static org.hamcrest.Matchers.*; public class ExemploRestAssured { @Test public void boasVindas() { given(). param("nome", "Elias"). when(). post("/cadastro"). then(). body("mensagem", is("Olá Elias")); } }
  • 12. Rest-Assured http://rest-assured.io DSL Java para testar e validar APIs REST. import static io.restassured.RestAssured.*; import static org.hamcrest.Matchers.*; public class ExemploRestAssured { @Test public void boasVindas() { given(). param("nome", "Elias"). when(). post("/cadastro"). then(). body("mensagem", is("Olá Elias")); } } importação das bibliotecas necessárias
  • 13. Rest-Assured http://rest-assured.io DSL Java para testar e validar APIs REST. import static io.restassured.RestAssured.*; import static org.hamcrest.Matchers.*; public class ExemploRestAssured { @Test public void boasVindas() { given(). param("nome", "Elias"). when(). post("/cadastro"). then(). body("mensagem", is("Olá Elias")); } } pré-condição para a requisição
  • 14. Rest-Assured http://rest-assured.io DSL Java para testar e validar APIs REST. import static io.restassured.RestAssured.*; import static org.hamcrest.Matchers.*; public class ExemploRestAssured { @Test public void boasVindas() { given(). param("nome", "Elias"). when(). post("/cadastro"). then(). body("mensagem", is("Olá Elias")); } } ação (requisição)
  • 15. Rest-Assured http://rest-assured.io DSL Java para testar e validar APIs REST. import static io.restassured.RestAssured.*; import static org.hamcrest.Matchers.*; public class ExemploRestAssured { @Test public void boasVindas() { given(). param("nome", "Elias"). when(). post("/cadastro"). then(). body("mensagem", is("Olá Elias")); } } validação dos dados de retorno
  • 16. Gerar do Access-Token public String getAccessToken() { Configuration configuration = new Configuration(); String username = configuration.getAuthenticationUsername(); String password = configuration.getAuthenticationPassword(); baseURI = configuration.getAPIURL(); basePath = configuration.getAPIOauthContext(); return given(). auth() .preemptive().basic(username, password). when(). get("/"). then(). extract(). path("Access-Token"); }
  • 17. Gerar do Access-Token public String getAccessToken() { Configuration configuration = new Configuration(); String username = configuration.getAuthenticationUsername(); String password = configuration.getAuthenticationPassword(); baseURI = configuration.getAPIURL(); basePath = configuration.getAPIOauthContext(); return given(). auth() .preemptive().basic(username, password). when(). get("/"). then(). extract(). path("Access-Token"); } a autenticação é enviada como pré-condição
  • 18. Gerar do Access-Token public String getAccessToken() { Configuration configuration = new Configuration(); String username = configuration.getAuthenticationUsername(); String password = configuration.getAuthenticationPassword(); baseURI = configuration.getAPIURL(); basePath = configuration.getAPIOauthContext(); return given(). auth() .preemptive().basic(username, password). when(). get("/"). then(). extract(). path("Access-Token"); } access-token é retornado como String
  • 19. Leitor de Configurações public String getAccessToken() { Configuration configuration = new Configuration(); String username = configuration.getAuthenticationUsername(); String password = configuration.getAuthenticationPassword(); baseURI = configuration.getAPIURL(); basePath = configuration.getAPIOauthContext(); return given(). auth() .preemptive().basic(username, password). when(). get("/"). then(). extract(). path("Access-Token"); } lê dados de autenticação
  • 20. Leitor de Configurações public String getAccessToken() { Configuration configuration = new Configuration(); String username = configuration.getAuthenticationUsername(); String password = configuration.getAuthenticationPassword(); baseURI = configuration.getAPIURL(); basePath = configuration.getAPIOauthContext(); return given(). auth() .preemptive().basic(username, password). when(). get("/"). then(). extract(). path("Access-Token"); } lê dados para acesso ao token
  • 21. BaseTest public abstract class BaseTest { protected static String accessToken; @BeforeClass(alwaysRun = true) public static void beforeClass() { Configuration configuration = new Configuration(); accessToken = new GenerateAccessToken(). getAccessToken(); baseURI = configuration.getAPIURL(); basePath = configuration.getPublico(); } } Código executado antes de cada teste
  • 22. BaseTest public abstract class BaseTest { protected static String accessToken; @BeforeClass(alwaysRun = true) public static void beforeClass() { Configuration configuration = new Configuration(); accessToken = new GenerateAccessToken(). getAccessToken(); baseURI = configuration.getAPIURL(); basePath = configuration.getPublico(); } } Código executado antes de cada teste accessToken que será acessado pelas classes de teste
  • 23. BaseTest public abstract class BaseTest { protected static String accessToken; @BeforeClass(alwaysRun = true) public static void beforeClass() { Configuration configuration = new Configuration(); accessToken = new GenerateAccessToken(). getAccessToken(); baseURI = configuration.getAPIURL(); basePath = configuration.getPublico(); } } Código executado antes de cada teste gera o accessToken apenas uma vez para a execução de teste
  • 24. BaseTest public abstract class BaseTest { protected static String accessToken; @BeforeClass(alwaysRun = true) public static void beforeClass() { Configuration configuration = new Configuration(); accessToken = new GenerateAccessToken(). getAccessToken(); baseURI = configuration.getAPIURL(); basePath = configuration.getPublico(); } } Código executado antes de cada teste chamada da classe de geração do token
  • 25. BaseTest public abstract class BaseTest { protected static String accessToken; @BeforeClass(alwaysRun = true) public static void beforeClass() { Configuration configuration = new Configuration(); accessToken = new GenerateAccessToken(). getAccessToken(); baseURI = configuration.getAPIURL(); basePath = configuration.getPublico(); } } Código executado antes de cada teste necessário informar novamente o apontamento da API * porque já foi apontado na geração do access token
  • 26. health-check Garantir que o endpoint está respondendo
  • 27. heath-check Apenas validamos se o statuscode é 200 public class EventoDetalheTest extends BaseTest { @Test(groups = { "health" }) public void healthCheck() { given(). auth().oauth2(accessToken). when(). get("evento/{id}", id). then(). statusCode(HttpStatus.SC_OK); }
  • 28. heath-check Apenas validamos se o statuscode é 200 tag de grupo para filtrar a execução public class EventoDetalheTest extends BaseTest { @Test(groups = { "health" }) public void healthCheck() { given(). auth().oauth2(accessToken). when(). get("evento/{id}", id). then(). statusCode(HttpStatus.SC_OK); }
  • 29. heath-check Apenas validamos se o statuscode é 200 public class EventoDetalheTest extends BaseTest { @Test(groups = { "health" }) public void healthCheck() { given(). auth().oauth2(accessToken). when(). get("evento/{id}", id). then(). statusCode(HttpStatus.SC_OK); } inserir sempre o access-token como pré-condição da requisição
  • 30. heath-check Apenas validamos se o statuscode é 200 public class EventoDetalheTest extends BaseTest { @Test(groups = { "health" }) public void healthCheck() { given(). auth().oauth2(accessToken). when(). get("evento/{id}", id). then(). statusCode(HttpStatus.SC_OK); } validar se o status code, e somente ele, é 200
  • 31. contrato Garantir que o endpoint não teve seus atributos alterados
  • 32. ● É o nome dado ao pacto entre o produtor e consumidor ● Garante que mudanças na API não invalidem o consumo: ● path ● parâmetros ● dados de envio (request) ● dados de retorno (response body) ● json-schema é um contrato que define os dados esperados, tipos e formatos de cada campo na resposta contrato
  • 33. { "nome": "Elias", "idade": 36 } { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "properties": { "nome": { "type": "string" }, "idade": { "type": "integer" } }, "required": [ "nome", "idade" ], "additionalProperties": false } json-schema
  • 34. { "nome": "Elias", "idade": 36 } { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "properties": { "nome": { "type": "string" }, "idade": { "type": "integer" } }, "required": [ "nome", "idade" ], "additionalProperties": false } json-schema possui o nome do atributo e o tipo de dados json-schema
  • 35. { "nome": "Elias", "idade": 36 } { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "properties": { "nome": { "type": "string" }, "idade": { "type": "integer" } }, "required": [ "nome", "idade" ], "additionalProperties": false } os dois atributos devem estar presentes, obrigatoriamente json-schema
  • 36. { "nome": "Elias", "idade": 36 } { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "properties": { "nome": { "type": "string" }, "idade": { "type": "integer" } }, "required": [ "nome", "idade" ], "additionalProperties": false } json-schema nenhum outro atributo é permitido
  • 37. teste funcional na minha máquina funciona ¯_(ツ)_/¯
  • 38. funcional Validar cenários positivos e negativos (caminho feliz | fluxo exceção) @Test(groups = {"funcional"}) public void validarEventoPeloID() { given(). auth().oauth2(accessToken). when(). get("evento/{id}", 110). then(). statusCode(HttpStatus.SC_OK). body("[0].id", is(110)). body("[0].descricao", is("TDC 2019 Florianópolis")). body("[0].chave", is("tdc-2019-florianopolis")). body("[0].ativo", is(true)). body("[0].dias", is(5)). body("[0].dataInicio", is("2019-04-23 00:00:00")). body("[0].dataTermino", is("2019-04-27 00:00:00")); }
  • 39. funcional Validar cenários positivos e negativos (caminho feliz | fluxo exceção) @Test(groups = {"funcional"}) public void validarEventoPeloID() { given(). auth().oauth2(accessToken). when(). get("evento/{id}", 110). then(). statusCode(HttpStatus.SC_OK). body("[0].id", is(110)). body("[0].descricao", is("TDC 2019 Florianópolis")). body("[0].chave", is("tdc-2019-florianopolis")). body("[0].ativo", is(true)). body("[0].dias", is(5)). body("[0].dataInicio", is("2019-04-23 00:00:00")). body("[0].dataTermino", is("2019-04-27 00:00:00")); } tag de grupo para filtrar a execução
  • 40. funcional Validar cenários positivos e negativos (caminho feliz | fluxo exceção) @Test(groups = {"funcional"}) public void validarEventoPeloID() { given(). auth().oauth2(accessToken). when(). get("evento/{id}", 110). then(). statusCode(HttpStatus.SC_OK). body("[0].id", is(110)). body("[0].descricao", is("TDC 2019 Florianópolis")). body("[0].chave", is("tdc-2019-florianopolis")). body("[0].ativo", is(true)). body("[0].dias", is(5)). body("[0].dataInicio", is("2019-04-23 00:00:00")). body("[0].dataTermino", is("2019-04-27 00:00:00")); } requisição para obter o detalhe de um determinado evento
  • 41. funcional Validar cenários positivos e negativos (caminho feliz | fluxo exceção) @Test(groups = {"funcional"}) public void validarEventoPeloID() { given(). auth().oauth2(accessToken). when(). get("evento/{id}", 110). then(). statusCode(HttpStatus.SC_OK). body("[0].id", is(110)). body("[0].descricao", is("TDC 2019 Florianópolis")). body("[0].chave", is("tdc-2019-florianopolis")). body("[0].ativo", is(true)). body("[0].dias", is(5)). body("[0].dataInicio", is("2019-04-23 00:00:00")). body("[0].dataTermino", is("2019-04-27 00:00:00")); } validação dos dados de retorno (body) * [0] existe ali porque o retorno é um array de uma posição * é um possível bug da implementação
  • 42. aceitação Garantir que um conjunto de endpoints funcionam como na UI
  • 43. Testar com a perspectiva do usuário ● Entrar no evento The Developers Conference Florianópolis ● Clicar na Trilha DevTest ● Visualizar os detalhes da palestra Automação e Virtualização de serviços REST com RestAssured + Wiremock + Docker
  • 44. aceitação @Test(groups = {"aceitacao"}) public void visualizaDetalhes_EncontrandoDados() throws ObjetoNotFoundException { String tituloPalestra = "Automação e Virtualização de serviços REST "; Evento evento = encontraEvento("TDC 2019 Florianópolis"); Modalidade trilha = encontraTrilha(evento, "Trilha DevTest"); Palestra palestra = encontraPalestra(evento, trilha, tituloPalestra); given(). auth(). oauth2(accessToken). pathParam("eventoID", evento.getId()). pathParam("modalidadeID", trilha.getId()). pathParam("palestraID", palestra.getId()). when(). get("/evento/{eventoID}/modalidade/{modalidadeID}/palestra/{palestraID}/palestrantes"). then(). statusCode(200). body("[0].member.nome", is("Elias Nogueira")). body("[0].member.empresa", is("Sicredi")); }
  • 45. aceitação @Test(groups = {"aceitacao"}) public void visualizaDetalhes_EncontrandoDados() throws ObjetoNotFoundException { String tituloPalestra = "Automação e Virtualização de serviços REST "; Evento evento = encontraEvento("TDC 2019 Florianópolis"); Modalidade trilha = encontraTrilha(evento, "Trilha DevTest"); Palestra palestra = encontraPalestra(evento, trilha, tituloPalestra); given(). auth(). oauth2(accessToken). pathParam("eventoID", evento.getId()). pathParam("modalidadeID", trilha.getId()). pathParam("palestraID", palestra.getId()). when(). get("/evento/{eventoID}/modalidade/{modalidadeID}/palestra/{palestraID}/palestrantes"). then(). statusCode(200). body("[0].member.nome", is("Elias Nogueira")). body("[0].member.empresa", is("Sicredi")); } funções auxiliares para chamadas a cada ação do usuário
  • 46. aceitação @Test(groups = {"aceitacao"}) public void visualizaDetalhes_EncontrandoDados() throws ObjetoNotFoundException { String tituloPalestra = "Automação e Virtualização de serviços REST "; Evento evento = encontraEvento("TDC 2019 Florianópolis"); Modalidade trilha = encontraTrilha(evento, "Trilha DevTest"); Palestra palestra = encontraPalestra(evento, trilha, tituloPalestra); given(). auth(). oauth2(accessToken). pathParam("eventoID", evento.getId()). pathParam("modalidadeID", trilha.getId()). pathParam("palestraID", palestra.getId()). when(). get("/evento/{eventoID}/modalidade/{modalidadeID}/palestra/{palestraID}/palestrantes"). then(). statusCode(200). body("[0].member.nome", is("Elias Nogueira")). body("[0].member.empresa", is("Sicredi")); } uso dos dados obtidos para a requisição
  • 48. ● Forma de simular comportamentos de componentes baseados em API ● Habilita o uso de serviços virtuais ao invés de serviços de produção mesmo que componentes chave não estejam disponíveis service virtualization
  • 49. service virtualization { "id" : "f238845e-369c-4a34-aa69-04d9240d7fb5", "name" : "evento_modalidade_palestra", "request" : { "urlPattern" : "/v1/publico/evento/110/modalidade/2685/palestra/10083", "method" : "GET" }, "response" : { "status" : 200, "bodyFileName" : "evento_modalidade_palestra_data.json", "headers" : { "Authorization": "*" } }, "persistent" : true, "insertionIndex" : 1 }
  • 50. service virtualization { "id" : "f238845e-369c-4a34-aa69-04d9240d7fb5", "name" : "evento_modalidade_palestra", "request" : { "urlPattern" : "/v1/publico/evento/110/modalidade/2685/palestra/10083", "method" : "GET" }, "response" : { "status" : 200, "bodyFileName" : "evento_modalidade_palestra_data.json", "headers" : { "Authorization": "*" } }, "persistent" : true, "insertionIndex" : 1 } sempre que uma requisição for igual a esta URL (inclusive com os dados)
  • 51. service virtualization { "id" : "f238845e-369c-4a34-aa69-04d9240d7fb5", "name" : "evento_modalidade_palestra", "request" : { "urlPattern" : "/v1/publico/evento/110/modalidade/2685/palestra/10083", "method" : "GET" }, "response" : { "status" : 200, "bodyFileName" : "evento_modalidade_palestra_data.json", }, "persistent" : true, "insertionIndex" : 1 } a api responderá os dados abaixo...
  • 52. service virtualization { "id": 10083, "slot": 6, "ordem": 1, "titulo": "Automação e Virtualização de serviços REST...", "descricao": ”Você sempre quis aprender a como testar...", "tipo": 2, "horario": "16:40" } dados de retorno