diff --git a/README.md b/README.md index 0d32edf..5b425df 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,119 @@ #### Selecione sua Língua | Select your Language -Português -English +Português +English -# Nome do Video +# Automação de Testes de API usando BDD ## Repositório ### Branches -* Master (contém o código do último vídeo) - `pt-master` -* Vídeo 1 - `` +* Master (contém o código do último vídeo) - `main` +* Vídeo 1 - `01-basic-gradle-project` +* Vídeo 2 - `02-first-feature` +* Vídeo 3 - `03-first-implementation` +* Vídeo 4 - `04-docstring` +* Vídeo 5 - `05-rest-assured-setup` +* Vídeo 6 - `06-specification-by-example-scenario` +* Vídeo 7 - `07-cucumber-runner` +* Vídeo 8 - `08-creating-clients` +* Vídeo 9 - `09-cucumber-hooks` +* Vídeo 10 - `10-deleting-user-with-hooks` +* Vídeo 11 - `11-mapping-more-complex-jsons` +* Vídeo 12 - `12-deserialization` +* Vídeo 13 - `13-understanding-restassured-syntax` +* Vídeo 14 - `14-using-restassured-assertions` +* Vídeo 15 - `15-prepping-the-scenario` +* Vídeo 16 - `16-scenario-outline` +* Vídeo 17 - `17-asserting-with-groovy-collection` +* Vídeo 18 - `18-creating-builder-from-scratch` ### Requisitos: -* +* Java 14 +* Gradle 6.7 +* Docker -### Comandos -* +### Executar os Testes Localmente +* Subir a loja Swagger Pet Store - `docker run --name petstore -d -p 12345:8080 swaggerapi/petstore3:unstable` +* Rodar os testes - `./gradlew test` +* Relatório do Cucumber - `app/build/reports/feature.html` ## Vídeo -* [Vídeo Completo]() - +1. [Criação do Projeto e suas dependências](https://youtu.be/YTKIVemoibA) +1. [Criando Primerio Cenário](https://youtu.be/dmSimWz21RQ) +1. [Codando o Primeiro Cenário](https://youtu.be/qJyYvdAYZzY) +1. [Devo usar DocString?](https://youtu.be/FVssrtDRs_o) +1. [Rest Assured config](https://youtu.be/Ca_z5m_GtpI) +1. [Especificação por exemplo na prática](https://youtu.be/yZA65qXKxoQ) +1. [criando o Executável](https://youtu.be/jSWksLZ9Z7M) +1. [Mapeando a API](https://youtu.be/ltgVZ8Pbjcc) +1. [Criando Ganchos](https://youtu.be/TWkmPkelLd4) +1. [Limpando os Dados](https://youtu.be/TWkmPkelLd4) +1. [Json mais complexos](https://youtu.be/ORZwGUocE4E) +1. [Desserialização](https://youtu.be/JJtHzUfo8us) +1. [Sintaxe antiga do RestAssured](https://youtu.be/b-yLVlV8zrs) +1. [Assertivas no RestAssured](https://youtu.be/hKuIhFwAhr0) +1. [Preparando o cenário](https://youtu.be/CMXwL-w4pMg) +1. [Esquema de Cenário](https://youtu.be/oK_mxMqArHw) +1. [Assertivas com Coleções Groovy](https://youtu.be/Eox-7q2gT0g) +1. [Criando builders do zero](https://youtu.be/vhbtYHOMIhE) --- -# Video Name +# API Test Automation using BDD ## Repository ### Branches -* Master (has the code from the last video) - `en-master` -* Video 1 - `` +* Master (has the code from the last video) - `main` +* Video 1 - `01-basic-gradle-project` +* Video 2 - `02-first-feature` +* Video 3 - `03-first-implementation` +* Video 4 - `04-docstring` +* Video 5 - `05-rest-assured-setup` +* Video 6 - `06-specification-by-example-scenario` +* Video 7 - `07-cucumber-runner` +* Video 8 - `08-creating-clients` +* Video 9 - `09-cucumber-hooks` +* Video 10 - `10-deleting-user-with-hooks` +* Video 11 - `11-mapping-more-complex-jsons` +* Video 12 - `12-deserialization` +* Video 13 - `13-understanding-restassured-syntax` +* Video 14 - `14-using-restassured-assertions` +* Video 15 - `15-prepping-the-scenario` +* Video 16 - `16-scenario-outline` +* Video 17 - `17-asserting-with-groovy-collection` +* Video 18 - `18-creating-builder-from-scratch` ### Requirements: -* +* Java 14 +* Gradle 6.7 +* Docker -### Commands -* +### Executing the tests locally +* Start Swagger Pet Store - `docker run --name petstore -d -p 12345:8080 swaggerapi/petstore3:unstable` +* Run the tests - `./gradlew test` +* Cucumber Report - `app/build/reports/feature.html` ## Video -* [Full Video]() - +1. [Creating the Project and its dependencies](https://youtu.be/0i72N1Fz_y0) +1. [Creating the First Scenario](https://youtu.be/A3uiR4quZr4) +1. [Implementing the First Scenario](https://youtu.be/uFoq-XtbBa0) +1. [Should I use DocString?](https://youtu.be/M-S55a6ei1M) +1. [Rest Assured Config](https://youtu.be/3jHMpmZfylY) +1. [Hands on Specification by Example](https://youtu.be/wkePKVTevYM) +1. [Runner Class](https://youtu.be/oqElg0mpfwY) +1. [Mapping the API](https://youtu.be/-_B2fFxfFdY) +1. [Creating Hooks](https://youtu.be/Kg611Jv_ib8) +1. [Cleaning the Data](https://youtu.be/IkH6gk2gNNQ) +1. [More complex jsons](https://youtu.be/YLuGI-j61MY) +1. [Deserialization](https://youtu.be/dKdenUAI6iA) +1. [RestAssured Old Syntax](https://youtu.be/1QUjfMs74bA) +1. [RestAssured assertions](https://youtu.be/3R74ESRKm7o) +1. [Preparing the scenario](https://youtu.be/kEuTzrOjdhY) +1. [Scenario Outline](https://youtu.be/ADRZoO5bUZw) +1. [Assertions with Groovy Collections](https://youtu.be/Hzv0Rj3wOBY) +1. [Creating builder from scratch](https://youtu.be/NDPm0ybVj2Q) diff --git a/app/src/test/java/bdd/automation/api/steps/PetStepDefinitions.java b/app/src/test/java/bdd/automation/api/steps/PetStepDefinitions.java new file mode 100644 index 0000000..b700387 --- /dev/null +++ b/app/src/test/java/bdd/automation/api/steps/PetStepDefinitions.java @@ -0,0 +1,103 @@ +package bdd.automation.api.steps; + +import bdd.automation.api.support.api.PetApi; +import bdd.automation.api.support.domain.Pet; +import io.cucumber.java.en.And; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; +import io.cucumber.java.pt.*; +import io.restassured.response.Response; +import org.apache.http.HttpStatus; + +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; + +public class PetStepDefinitions { + + private PetApi petApi; + private List actualPets; + private Response actualPetsResponse; + + public PetStepDefinitions() { + petApi = new PetApi(); + } + + @Given("that I have pets {word}") + @Dado("que eu possua animais {word}") + public void thatIHavePetsAvailable(String status) {} + + @When("I search for all pets {word}") + @Quando("eu pesquiso por todos os animais {word}") + public void iSearchForAllPetsAvailable(String status) { + actualPets = petApi.getPetsByStatus(status); + } + + @Then("I receive a list of pets available") + @Entao("eu recebo a lista de animais available") + public void iReceiveAListOfPetsAvailable() { + assertThat(actualPets, is(not(empty()))); + } + + @And("I receive another list of pets {word}") + @E("eu recebo uma outra lista de animais {word}") + public void iReceiveAnotherListOfPetsAvailable(String status) { + Response actualPetsResponse = petApi.getPetsResponseByStatus(status); + + actualPets = actualPetsResponse.body().jsonPath().getList("", Pet.class); + + actualPetsResponse. + then(). + statusCode(HttpStatus.SC_OK). + body( + "size()", is(actualPets.size()), + "findAll { it.status == '"+status+"' }.size()", is(actualPets.size()) + ); + + } + + @Então("eu recebo a lista com {} animal/animais") + @Then("I receive a list of {} pet(s)") + public void iReceiveAListOfPets(int petsQuantity) { + assertThat(actualPets.size(), is(petsQuantity)); + } + + @Dado("que eu não possua animais {word}") + @Given("that I don't have pets {word}") + public void thatIdontHavePets(String status) { + petApi.deletePetsByStatus(status); + } + + @When("I do a search for all pets {word}") + @Quando("pesquiso por todos os animais {word}") + public void iDoASearchForAllPetsAvailable(String status) { + actualPetsResponse = petApi.getPetsResponseByStatus(status); + } + + @Then("I receive a list of {int} pets {word}") + @Entao("recebo a lista com {int} animais {word}") + public void iReceiveAListOfPetsAvailable(int petsQuantity, String status) { + actualPetsResponse. + then(). + statusCode(HttpStatus.SC_OK). + body( + "size()", is(petsQuantity), + "findAll { it.status == '" + status + "' }.size()", is(petsQuantity) + ); + + } + + @And("{int} pets has the name {word}") + @E("{int} animais possuem o nome {word}") + public void petsHasTheNameLion(int petsQuantity, String petName) { + actualPetsResponse. + then(). + body( + "findAll { it.name.contains('"+petName+"') }.size()", is(petsQuantity) + ); + } +} diff --git a/app/src/test/java/bdd/automation/api/steps/StoreStepDefinitions.java b/app/src/test/java/bdd/automation/api/steps/StoreStepDefinitions.java new file mode 100644 index 0000000..723db2b --- /dev/null +++ b/app/src/test/java/bdd/automation/api/steps/StoreStepDefinitions.java @@ -0,0 +1,30 @@ +package bdd.automation.api.steps; + +import bdd.automation.api.support.domain.Store; +import bdd.automation.api.support.domain.builders.StoreBuilder; +import io.cucumber.java.en.Given; + +public class StoreStepDefinitions { + @Given("something..") + public void something() { + Store store1 = new StoreBuilder().build(); + + Store store2 = new StoreBuilder() + .withId(9) + .withPetId(99) + .withQuantity(100) + .withStatus("complete") + .withShipDate("11/01/2021") + .build(); + + Store store3 = new StoreBuilder() + .withPetId(88) + .withQuantity(50) + .withStatus("incomplete") + .build(); + + Store store4 = new StoreBuilder().build(); + + System.out.println("asdasd"); + } +} diff --git a/app/src/test/java/bdd/automation/api/steps/UserStepDefinitions.java b/app/src/test/java/bdd/automation/api/steps/UserStepDefinitions.java index 8e9680c..c96ea2f 100644 --- a/app/src/test/java/bdd/automation/api/steps/UserStepDefinitions.java +++ b/app/src/test/java/bdd/automation/api/steps/UserStepDefinitions.java @@ -15,7 +15,7 @@ public class UserStepDefinitions { private User expectedUser; - private final UserApi userApi; + private UserApi userApi; public UserStepDefinitions() { userApi = new UserApi(); diff --git a/app/src/test/java/bdd/automation/api/support/api/PetApi.java b/app/src/test/java/bdd/automation/api/support/api/PetApi.java new file mode 100644 index 0000000..f83a3d2 --- /dev/null +++ b/app/src/test/java/bdd/automation/api/support/api/PetApi.java @@ -0,0 +1,47 @@ +package bdd.automation.api.support.api; + +import bdd.automation.api.support.domain.Pet; +import io.restassured.response.Response; + +import java.util.List; + +import static io.restassured.RestAssured.*; + +public class PetApi { + + private static final String FIND_PETS_BY_STATUS_ENDPOINT = "v3/pet/findByStatus?status={status}"; + private static final String PET_ENDPOINT = "v3/pet/{id}"; + + public List getPetsByStatus(String status) { + return given(). + pathParam("status", status). + when(). + get(FIND_PETS_BY_STATUS_ENDPOINT). + then(). + extract().body().jsonPath().getList("", Pet.class); + } + + public Response getPetsResponseByStatus(String status) { + return given(). + pathParam("status", status). + when(). + get(FIND_PETS_BY_STATUS_ENDPOINT); + } + + public void deletePetsByStatus(String status) { + List petsId = given(). + pathParam("status", status). + when(). + get(FIND_PETS_BY_STATUS_ENDPOINT). + thenReturn(). + path("id"); + + if(!petsId.isEmpty()) { + for (Integer id : petsId) { + given().pathParam("id", id).delete(PET_ENDPOINT); + } + } + + } + +} diff --git a/app/src/test/java/bdd/automation/api/support/api/UserApi.java b/app/src/test/java/bdd/automation/api/support/api/UserApi.java index ba73789..63354e2 100644 --- a/app/src/test/java/bdd/automation/api/support/api/UserApi.java +++ b/app/src/test/java/bdd/automation/api/support/api/UserApi.java @@ -1,6 +1,7 @@ package bdd.automation.api.support.api; import bdd.automation.api.support.domain.User; +import lombok.Data; import org.apache.http.HttpStatus; import java.util.Arrays; @@ -8,12 +9,14 @@ import static io.restassured.RestAssured.given; - +@Data public class UserApi { private static final String CREATE_USER_ENDPOINT = "/v3/user"; private static final String USER_ENDPOINT = "/v3/user/{name}"; + private String name; + public void createUser(User user) { given(). body(user). diff --git a/app/src/test/java/bdd/automation/api/support/domain/Pet.java b/app/src/test/java/bdd/automation/api/support/domain/Pet.java new file mode 100644 index 0000000..29737b2 --- /dev/null +++ b/app/src/test/java/bdd/automation/api/support/domain/Pet.java @@ -0,0 +1,39 @@ +package bdd.automation.api.support.domain; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Arrays; +import java.util.List; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class Pet { + @Builder.Default + private int id = 2; + @Builder.Default + private String name = "Panthro"; + @Builder.Default + private Category category = new Category(); + @Builder.Default + private String status = "available"; + @Builder.Default + private List photoUrls = Arrays.asList("url1", "url2"); + + @Data + @Builder + @AllArgsConstructor + @NoArgsConstructor + public static class Category { + @Builder.Default + private int id = 2; + @Builder.Default + private String name = "Cats"; + } +} diff --git a/app/src/test/java/bdd/automation/api/support/domain/Store.java b/app/src/test/java/bdd/automation/api/support/domain/Store.java new file mode 100644 index 0000000..3f7a53a --- /dev/null +++ b/app/src/test/java/bdd/automation/api/support/domain/Store.java @@ -0,0 +1,17 @@ +package bdd.automation.api.support.domain; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +@Data +public class Store { + private int id; + private int petId; + private int quantity; + private String shipDate; + private String status; + private boolean complete; +} diff --git a/app/src/test/java/bdd/automation/api/support/domain/builders/StoreBuilder.java b/app/src/test/java/bdd/automation/api/support/domain/builders/StoreBuilder.java new file mode 100644 index 0000000..40c857f --- /dev/null +++ b/app/src/test/java/bdd/automation/api/support/domain/builders/StoreBuilder.java @@ -0,0 +1,66 @@ +package bdd.automation.api.support.domain.builders; + +import bdd.automation.api.support.domain.Store; + +public class StoreBuilder { + private int id; + private int petId; + private int quantity; + private String shipDate; + private String status; + private boolean complete; + + public StoreBuilder() { + reset(); + } + + public StoreBuilder withId(int id) { + this.id = id; + return this; + } + + public StoreBuilder withPetId(int petId) { + this.petId = petId; + return this; + } + + public StoreBuilder withQuantity(int quantity) { + this.quantity = quantity; + return this; + } + + public StoreBuilder withShipDate(String shipDate) { + this.shipDate = shipDate; + return this; + } + + public StoreBuilder withStatus(String status) { + this.status = status; + return this; + } + + public StoreBuilder withComplete(Boolean complete) { + this.complete = complete; + return this; + } + + public Store build() { + return new Store( + id, + petId, + quantity, + shipDate, + status, + complete + ); + } + + public void reset() { + id = 5; + petId = 22; + quantity = 10; + shipDate = "20/02/2021"; + status = "approved"; + complete = true; + } +} diff --git a/app/src/test/resources/features/animal.feature b/app/src/test/resources/features/animal.feature new file mode 100644 index 0000000..d9c9494 --- /dev/null +++ b/app/src/test/resources/features/animal.feature @@ -0,0 +1,39 @@ +# language: pt +Funcionalidade: Gerenciamento de um animal da PetSore + + Cenario: Lista somente animais disponíveis para a venda + Dado que eu possua animais available + Quando eu pesquiso por todos os animais available + Então eu recebo a lista de animais available +# Passo duplicado, somente para exemplo + E eu recebo uma outra lista de animais available + + Cenario: Lista somente animais pending + Dado que eu possua animais pending + Quando eu pesquiso por todos os animais pending + Então eu recebo a lista com 2 animais + + Cenario: Não lista nenhum animal + Dado que eu não possua animais sold + Quando eu pesquiso por todos os animais sold + Então eu recebo a lista com 0 animal + + Esquema do Cenário: Lista animais pelo seu estado de venda + Dado que eu não possua animais sold + Quando eu pesquiso por todos os animais + Então eu recebo a lista com animais + + Exemplos: Animais em estoque + | estado | quantidade | + | available | 7 | + | pending | 2 | + + Exemplos: Animais sem estoque + | estado | quantidade | + | sold | 0 | + + Cenario: Lista animais disponíveis para a venda + Dado que eu possua animais available + Quando pesquiso por todos os animais available + Então recebo a lista com 7 animais available + E 3 animais possuem o nome Lion \ No newline at end of file diff --git a/app/src/test/resources/features/loja.feature b/app/src/test/resources/features/loja.feature new file mode 100644 index 0000000..d3864d7 --- /dev/null +++ b/app/src/test/resources/features/loja.feature @@ -0,0 +1,7 @@ +# new feature +# Tags: optional + +Feature: A description + + Scenario: A scenario + Given something.. \ No newline at end of file diff --git a/app/src/test/resources/features/pet.feature b/app/src/test/resources/features/pet.feature new file mode 100644 index 0000000..28432f3 --- /dev/null +++ b/app/src/test/resources/features/pet.feature @@ -0,0 +1,38 @@ +Feature: Manage a pet in the PetSore + + Scenario: List only available pets for sale + Given that I have pets available + When I search for all pets available + Then I receive a list of pets available +# Duplicated step, done for the sake of learning + And I receive another list of pets available + + Scenario: List only pets pending + Given that I have pets pending + When I search for all pets pending + Then I receive a list of 2 pets + + Scenario: Don't list any pets + Given that I don't have pets sold + When I search for all pets sold + Then I receive a list of 0 pet + + Scenario Outline: List pets by its selling state + Given that I don't have pets sold + When I search for all pets + Then I receive a list of pets + + Examples: Pets in stock + | status | quantity | + | available | 7 | + | pending | 2 | + + Examples: Pets not in stock + | status | quantity | + | sold | 0 | + + Scenario: List pets available for selling + Given that I have pets available + When I do a search for all pets available + Then I receive a list of 7 pets available + And 3 pets has the name Lion \ No newline at end of file diff --git a/app/src/test/resources/features/store.feature b/app/src/test/resources/features/store.feature new file mode 100644 index 0000000..d3864d7 --- /dev/null +++ b/app/src/test/resources/features/store.feature @@ -0,0 +1,7 @@ +# new feature +# Tags: optional + +Feature: A description + + Scenario: A scenario + Given something.. \ No newline at end of file