bdd-driven microservices
TRANSCRIPT
Tools, techniques and reflections
BDD-Driven Microservices in Java
John Ferguson
Smart
Introductions“I help teams of smart people
learn to work together more efficiently, to deliver better software faster”
Independently Deployable
Bounded ContextsLoosely CoupledSingle Responsibility
Microservices 101
Independently Deployable
Bounded ContextsLoosely CoupledSingle Responsibility
Microservices 101
Independently Deployable
Bounded ContextsLoosely CoupledSingle Responsibility
Microservices 101
Independently Deployable
Bounded ContextsLoosely CoupledSingle Responsibility
Microservices 101
Independently Deployable
Bounded ContextsLoosely CoupledSingle Responsibility
Microservices 101
Independently Deployable
Bounded ContextsLoosely CoupledSingle Responsibility
Microservices 101
Microservices 101
Movie Catalog Service
Authentication Service
Shopping Cart Service
Delivery Service
Payment Service
REST/HTTP
Microservices 101
Movie Catalog Service
Authentication Service
Shopping Cart Service
Delivery Service
Payment Service
Event Bus
Microservices 101Movie Catalog Service
Microservices 101Movie Catalog Service
Resource
Microservices 101Movie Catalog Service
Resource
Service Layer
Domain model
Repositories
Microservices 101Movie Catalog Service
Resource
Persistance Layer
Service Layer
Domain model
Repositories
Microservices 101Movie Catalog Service
Resource
Persistance Layer Gateway
Service Layer
Domain model
Repositories
The testing pyramid
The testing pyramid
Unit Tests
The testing pyramid
Unit Tests
Integration Tests
The testing pyramid
Unit Tests
Integration Tests
End-to-end Tests
The testing pyramid
Unit Tests
Integration Tests
End-to-end Tests
The testing pyramid
Unit Tests
Integration Tests
End-to-end Tests
The testing pyramid
Unit Tests
Integration Tests
End-to-end Tests
The testing pyramidMovie Catalog Service
Resource
Persistance Layer Gateway
Service Layer
Domain model
Repositories
Unit Tests
The testing pyramidMovie Catalog Service
Resource
Persistance Layer Gateway
Service Layer
Domain model
Repositories
Unit Tests
The testing pyramidMovie Catalog Service
Resource
Persistance Layer Gateway
Service Layer
Domain model
Repositories
Unit Tests
The testing pyramidMovie Catalog Service
Resource
Persistance Layer Gateway
Service Layer
Domain model
Repositories
Unit Tests
The testing pyramidMovie Catalog Service
Resource
Persistance Layer Gateway
Service Layer
Domain model
Repositories
Unit Tests
The testing pyramidMovie Catalog Service
Resource
Persistance Layer Gateway
Service Layer
Domain model
Repositories
Unit Tests
The testing pyramidMovie Catalog Service
Resource
Persistance Layer Gateway
Service Layer
Domain model
Repositories
Unit Tests
The testing pyramidMovie Catalog Service
Resource
Persistance Layer Gateway
Service Layer
Domain model
Repositories
Unit Tests
The testing pyramidMovie Catalog Service
Resource
Persistance Layer Gateway
Service Layer
Domain model
Repositories
Unit Tests
The testing pyramidMovie Catalog Service
Resource
Persistance Layer Gateway
Service Layer
Domain model
Repositories
Integration Tests
The testing pyramidMovie Catalog Service
Resource
Persistance Layer Gateway
Service Layer
Domain model
Repositories
Integration Tests
The testing pyramidMovie Catalog Service
Resource
Persistance Layer Gateway
Service Layer
Domain model
Repositories
Integration Tests
The testing pyramidMovie Catalog Service
Resource
Persistance Layer Gateway
Service Layer
Domain model
Repositories
Integration Tests
The testing pyramidMovie Catalog Service
Resource
Persistance Layer Gateway
Service Layer
Domain model
Repositories
Integration Tests
The testing pyramid End-to-End Tests
Movie Catalog Service
Authentication Service
Shopping Cart Service
Delivery Service
Payment Service
Event Bus
The testing pyramid End-to-End Tests
Movie Catalog Service
Authentication Service
Shopping Cart Service
Delivery Service
Payment Service
Event Bus
The testing pyramid End-to-End Tests
Movie Catalog Service
Authentication Service
Shopping Cart Service
Delivery Service
Payment Service
Event Bus
The testing pyramid End-to-End Tests
Movie Catalog Service
Authentication Service
Shopping Cart Service
Delivery Service
Payment Service
Event Bus
The testing pyramid End-to-End Tests
Movie Catalog Service
Authentication Service
Shopping Cart Service
Delivery Service
Payment Service
Event Bus
The testing pyramid End-to-End Tests
Movie Catalog Service
Authentication Service
Shopping Cart Service
Delivery Service
Payment Service
Event Bus
The testing pyramid End-to-End Tests
Movie Catalog Service
Authentication Service
Shopping Cart Service
Delivery Service
Payment Service
Event Bus
The testing pyramid
Unit Tests
Integration Tests
End-to-end Tests
The testing pyramid
Unit Tests
Integration Tests
End-to-end Tests
The testing pyramid
Unit Tests
Integration Tests
End-to-end Tests
The testing pyramid
Unit Tests
Integration Tests
End-to-end Tests
The testing pyramid
Unit Tests
Integration Tests
End-to-end Tests
The testing pyramid
Unit Tests
Integration Tests
End-to-end Tests
The testing pyramid
Unit Tests
Integration Tests
End-to-end Tests
The testing pyramid
Unit Tests
Integration Tests
End-to-end Tests
The testing pyramid
Unit Tests
Integration Tests
End-to-end Tests
The testing pyramid
Unit Tests
Integration Tests
End-to-end Tests
The testing pyramid
Unit Tests
Integration Tests
End-to-end Tests
To document how to interact with the microservice
To document what the microservice does
To ensure the service work correctly
To ensure we are delivering valuable functionality
Why test our micro-services?
To document how to interact with the microservice
To document what the microservice does
To ensure the service work correctly
To ensure we are delivering valuable functionality
Why test our micro-services?
To document how to interact with the microservice
To document what the microservice does
To ensure the service work correctly
To ensure we are delivering valuable functionality
Why test our micro-services?
To document how to interact with the microservice
To document what the microservice does
To ensure the service work correctly
To ensure we are delivering valuable functionality
Why test our micro-services?
To document how to interact with the microservice
To document what the microservice does
To ensure the service work correctly
To ensure we are delivering valuable functionality
Why test our micro-services?
To document how to interact with the microservice
To document what the microservice does
To ensure the service work correctly
To ensure we are delivering valuable functionality
Why test our micro-services?
“We test our micro-services so we can deploy new features quickly and with confidence”
And with aggressive production monitoring
Writing executable specifications and living documentation
To a sufficient level of confidence
Outside-in testing
Pragmatic web service testing
And with aggressive production monitoring
Writing executable specifications and living documentation
To a sufficient level of confidence
Outside-in testing
Pragmatic web service testing
And with aggressive production monitoring
Writing executable specifications and living documentation
To a sufficient level of confidence
Outside-in testing
Pragmatic web service testing
And with aggressive production monitoring
Writing executable specifications and living documentation
To a sufficient level of confidence
Outside-in testing
Pragmatic web service testing
And with aggressive production monitoring
Writing executable specifications and living documentation
To a sufficient level of confidence
Outside-in testing
Pragmatic web service testing
And with aggressive production monitoring
Writing executable specifications and living documentation
To a sufficient level of confidence
Outside-in testing
Pragmatic web service testing
Outside-in development
To deliver software that matters
And a common language to build a shared understanding
Using examples at multiple levels
Collaborate to discover requirements and identify uncertainty
The essence of BDD
To deliver software that matters
And a common language to build a shared understanding
Using examples at multiple levels
Collaborate to discover requirements and identify uncertainty
The essence of BDD
To deliver software that matters
And a common language to build a shared understanding
Using examples at multiple levels
Collaborate to discover requirements and identify uncertainty
The essence of BDD
To deliver software that matters
And a common language to build a shared understanding
Using examples at multiple levels
Collaborate to discover requirements and identify uncertainty
The essence of BDD
To deliver software that matters
And a common language to build a shared understanding
Using examples at multiple levels
Collaborate to discover requirements and identify uncertainty
The essence of BDD
The business owner tells the business
analyst what he wants
12 The business
analyst writes a requirements
document
3 The developer translates the requirements into software
4 The tester translates the requirements
into test cases 5 The technical writer translates
the software into functional and technical
documentationA traditional development process
How does it work?
The business owner tells the business
analyst what he wants
12 The business
analyst writes a requirements
document
3 The developer translates the requirements into software
4 The tester translates the requirements
into test cases 5 The technical writer translates
the software into functional and technical
documentationA traditional development process
How does it work?
The business owner tells the business
analyst what he wants
12 The business
analyst writes a requirements
document
3 The developer translates the requirements into software
4 The tester translates the requirements
into test cases 5 The technical writer translates
the software into functional and technical
documentationA traditional development process
How does it work?
The business owner tells the business
analyst what he wants
12 The business
analyst writes a requirements
document
3 The developer translates the requirements into software
4 The tester translates the requirements
into test cases 5 The technical writer translates
the software into functional and technical
documentationA traditional development process
How does it work?
The business owner tells the business
analyst what he wants
12 The business
analyst writes a requirements
document
3 The developer translates the requirements into software
4 The tester translates the requirements
into test cases 5 The technical writer translates
the software into functional and technical
documentationA traditional development process
How does it work?
The business owner tells the business
analyst what he wants
12 The business
analyst writes a requirements
document
3 The developer translates the requirements into software
4 The tester translates the requirements
into test cases 5 The technical writer translates
the software into functional and technical
documentationA traditional development process
How does it work?
The business owner tells the business
analyst what he wants
12 The business
analyst writes a requirements
document
3 The developer translates the requirements into software
4 The tester translates the requirements
into test cases 5 The technical writer translates
the software into functional and technical
documentationA traditional development process
How does it work?
The business owner tells the business
analyst what he wants
12 The business
analyst writes a requirements
document
3 The developer translates the requirements into software
4 The tester translates the requirements
into test cases 5 The technical writer translates
the software into functional and technical
documentationA traditional development process
How does it work?
The business owner tells the business
analyst what he wants
12 The business
analyst writes a requirements
document
3 The developer translates the requirements into software
4 The tester translates the requirements
into test cases 5 The technical writer translates
the software into functional and technical
documentationA traditional development process
How does it work?
The business owner tells the business
analyst what he wants
12 The business
analyst writes a requirements
document
3 The developer translates the requirements into software
4 The tester translates the requirements
into test cases 5 The technical writer translates
the software into functional and technical
documentationA traditional development process
How does it work?
The business owner tells the business
analyst what he wants
12 The business
analyst writes a requirements
document
3 The developer translates the requirements into software
4 The tester translates the requirements
into test cases 5 The technical writer translates
the software into functional and technical
documentationA traditional development process
How does it work?
How does it work?
The business owner and the business
analyst have a conversation about
what he needs.
1
2
3
4 The tester uses these scenarios as the basis for
her tests
5
The automated tests provide feedback on progress and help
document the application
The business analyst, the developer and the tester elaborate the
requirements together.
The scenarios guide the developer and act as
automated tests
They define requirements as
structured, English-language format
"scenarios"
A BDD development process
How does it work?
The business owner and the business
analyst have a conversation about
what he needs.
1
2
3
4 The tester uses these scenarios as the basis for
her tests
5
The automated tests provide feedback on progress and help
document the application
The business analyst, the developer and the tester elaborate the
requirements together.
The scenarios guide the developer and act as
automated tests
They define requirements as
structured, English-language format
"scenarios"
A BDD development process
How does it work?
The business owner and the business
analyst have a conversation about
what he needs.
1
2
3
4 The tester uses these scenarios as the basis for
her tests
5
The automated tests provide feedback on progress and help
document the application
The business analyst, the developer and the tester elaborate the
requirements together.
The scenarios guide the developer and act as
automated tests
They define requirements as
structured, English-language format
"scenarios"
A BDD development process
How does it work?
The business owner and the business
analyst have a conversation about
what he needs.
1
2
3
4 The tester uses these scenarios as the basis for
her tests
5
The automated tests provide feedback on progress and help
document the application
The business analyst, the developer and the tester elaborate the
requirements together.
The scenarios guide the developer and act as
automated tests
They define requirements as
structured, English-language format
"scenarios"
A BDD development process
How does it work?
The business owner and the business
analyst have a conversation about
what he needs.
1
2
3
4 The tester uses these scenarios as the basis for
her tests
5
The automated tests provide feedback on progress and help
document the application
The business analyst, the developer and the tester elaborate the
requirements together.
The scenarios guide the developer and act as
automated tests
They define requirements as
structured, English-language format
"scenarios"
A BDD development process
How does it work?
The business owner and the business
analyst have a conversation about
what he needs.
1
2
3
4 The tester uses these scenarios as the basis for
her tests
5
The automated tests provide feedback on progress and help
document the application
The business analyst, the developer and the tester elaborate the
requirements together.
The scenarios guide the developer and act as
automated tests
They define requirements as
structured, English-language format
"scenarios"
A BDD development process
But what about micro-services?
The testing pyramid
Unit Tests
Integration Tests
End-to-end Tests
The testing pyramid
Unit Tests
Integration Tests
End-to-end Tests
A different testing pyramid
A different testing pyramid
Executable Business Specifications
A different testing pyramid
Slow-running executable technical specifications
Executable Business Specifications
A different testing pyramid
Fast-running executable technical specifications
Slow-running executable technical specifications
Executable Business Specifications
Outside-in developmentMovie Catalog Service
Resource
Persistance Layer Gateway
Service Layer
Domain model
Repositories
Executable Business specs
Outside-in developmentMovie Catalog Service
Resource
Persistance Layer Gateway
Service Layer
Domain model
Repositories
Executable Business specs
Scenario: Search movies by director Given the catalog has the following movies: | title | director | actors | | Gladiator | Ridley Scott | Russel Crowe, Joaquin Phoenix | | Letters from Iwo Jima | Clint Eastwood | Ken Watanabe | | Gran Torino | Clint Eastwood | Clint Eastwood, Bee Vang | When I search for movies directed by Clint Eastwood Then I should be presented with the following movies: | title | director | actors | | Letters from Iwo Jima | Clint Eastwood | Ken Watanabe | | Gran Torino | Clint Eastwood | Clint Eastwood, Bee Vang |
Outside-in developmentMovie Catalog Service
Resource
Persistance Layer Gateway
Service Layer
Domain model
Repositories
Slow-running technical
specs
Outside-in developmentMovie Catalog Service
Resource
Persistance Layer Gateway
Service Layer
Domain model
Repositories
Slow-running technical
specs
@ContextConfiguration(loader = SpringApplicationContextLoader.class, classes = MovieServiceApplication.class) @WebAppConfiguration@IntegrationTest("server.port:0") class WhenFindingMoviesViaTheRestAPI extends Specification { @Autowired MovieRepository movieRepository; @Value('${local.server.port}') int port; def "should list movies by director"() { given: movieCatalogContains([GLADIATOR, LETTERS_FROM_IWO_JIMA, GRAN_TORINO]) when: List<Movie> movies = when().get("/movies/findByDirector/Clint Eastwood").as(List) then: movies.collect {movie -> movie.title} == ["Letters from Iwo Jima", "Gran Torino"] }
Outside-in developmentMovie Catalog Service
Resource
Persistance Layer Gateway
Service Layer
Domain model
Repositories
Fast-running
technical specs
Outside-in developmentMovie Catalog Service
Resource
Persistance Layer Gateway
Service Layer
Domain model
Repositories
Fast-running
technical specs
def setup() { repository = Mock(MovieRepository) artistService = new ArtistService(movieRepository: repository) } def "should build actor details from films the artist has acted in and has directed"() { given: "Clint Eastwood has directed some films" repository.findByDirector("Clint Eastwood") >> [LETTERS_FROM_IWO_JIMA, GRAN_TORINO] and: "Clint Eastwood has starred in some films" repository.findByActors("Clint Eastwood") >> [THE_GOOD_THE_BAD_AND_THE_UGLY, GRAN_TORINO] when: def artistDetails = artistService.findArtistByName("Clint Eastwood") then: artistDetails.isPresent() and: artistDetails.get().name == "Clint Eastwood" && artistDetails.get().filmsActedIn == [THE_GOOD_THE_BAD_AND_THE_UGLY, GRAN_TORINO] && artistDetails.get().filmsDirected == [LETTERS_FROM_IWO_JIMA, GRAN_TORINO] }
Spock
Outside-in developmentMovie Catalog Service
Resource
Persistance Layer Gateway
Service Layer
Domain model
Repositories
Fast-running
technical specs
Outside-in developmentMovie Catalog Service
Resource
Persistance Layer Gateway
Service Layer
Domain model
Repositories
Fast-running
technical specs
class MovieControllerSpecs extends Specification { MovieRepository movieRepository = Mock() def "should find by director regardless of case (#director -> #filteredDirector)"() { given: def controller = new MovieController(repository: movieRepository) when: controller.findByDirector(director) then: 1*movieRepository.findByDirector(filteredDirector) where: director | filteredDirector "Clint Eastwood" | "Clint Eastwood" "Clint eastwood" | "Clint Eastwood" Spock
Micro-service Contracts
Micro-service ContractsMovie Catalog Service
Resource
Persistance Layer Gateway
Service LayerDomain modelRepositories
Shopping Cart Service
Delivery Service
REST/HTTP
Micro-service ContractsMovie Catalog Service
Resource
Persistance Layer Gateway
Service LayerDomain modelRepositories
Shopping Cart Service
REST/HTTP
Delivery Service
Micro-service ContractsMovie Catalog Service
Resource
Persistance Layer Gateway
Service LayerDomain modelRepositories
Shopping Cart Service
REST/HTTP
Consumer
Delivery Service
Micro-service ContractsMovie Catalog Service
Resource
Persistance Layer Gateway
Service LayerDomain modelRepositories
Shopping Cart Service
REST/HTTP
ConsumerProducer
Delivery Service
Micro-service ContractsMovie Catalog Service
Resource
Persistance Layer Gateway
Service LayerDomain modelRepositories
Shopping Cart Service
REST/HTTP
ConsumerProducer
Contract
Delivery Service
Micro-service ContractsMovie Catalog Service
Resource
Persistance Layer Gateway
Service LayerDomain modelRepositories
Shopping Cart Service
REST/HTTPContract
Delivery Service
Micro-service ContractsMovie Catalog Service
Resource
Persistance Layer Gateway
Service LayerDomain modelRepositories
Shopping Cart Service
Delivery Service
REST/HTTPContract
Mock Shopping
Cart Service
Micro-service Contracts
REST/HTTP
Movie Catalog Service
Web app iPhone app Android app
Micro-service Contracts
REST/HTTP
Movie Catalog Service
Web app iPhone app Android app
Producer
Micro-service Contracts
REST/HTTP
Movie Catalog Service
Web app iPhone app Android app
Consumer
Producer
Micro-service Contracts
REST/HTTP
Movie Catalog Service
Web app iPhone app Android app
Consumer
Producer
Consumer
Micro-service Contracts
REST/HTTP
Movie Catalog Service
Web app iPhone app Android app
Consumer
Producer
Consumer Consumer
Micro-service Contracts
REST/HTTP
Movie Catalog Service
Web app iPhone app Android app
Consumer
Producer
Consumer Consumer
Contract
Micro-service Contracts
REST/HTTP
Movie Catalog Service
Web app iPhone app Android app
Consumer
Producer
Consumer Consumer
Contract
Micro-service Contracts
REST/HTTP
Movie Catalog Service
Web app iPhone app Android app
Consumer
Producer
Consumer Consumer
Contract
Micro-service Contracts
REST/HTTP
Movie Catalog Service
Web app iPhone app Android app
Consumer
Producer
Consumer Consumer
Contract
The Producer API is the sum of the Consumer Contrats
Specialised ToolsPublished specificationsAutomated Acceptance Criteria
Verifying the contracts
Specialised ToolsPublished specificationsAutomated Acceptance Criteria
Verifying the contracts
Specialised ToolsPublished specificationsAutomated Acceptance Criteria
Verifying the contracts
Specialised ToolsPublished specificationsAutomated Acceptance Criteria
Verifying the contracts
Specialised ToolsPublished specificationsAutomated Acceptance Criteria
Verifying the contracts
PactPactoMountebank
More specialised tools
PactPactoMountebank
More specialised tools
PactPactoMountebank
More specialised tools
PactPactoMountebank
More specialised tools
PactPactoMountebank
More specialised tools
PactPactoMountebank
More specialised tools
PactPactoMountebank
More specialised tools
Pact and PactoConsumer Driven Contract Testing
42
Consumer Movie Catalog Service
Pact and PactoConsumer Driven Contract Testing
42
Consumer Movie Catalog Service
Mock Service
Pact help us create and configure a mock service
Pact and PactoConsumer Driven Contract Testing
43
Consumer Movie Catalog Service
Mock Service
Pact and PactoConsumer Driven Contract Testing
43
Consumer Movie Catalog Service
Mock Service
We write specifications to describe the expected
behaviour of the service
Pact and PactoConsumer Driven Contract Testing
44
Consumer Movie Catalog Service
Mock Service
Pact and PactoConsumer Driven Contract Testing
44
Consumer Movie Catalog Service
Mock Service
When we run these specifications, the expected behaviour is recorded in a
‘pact’ file
asdasdasda asdasdasdas asdasdasd asdasdasda
asdasdasdasd asdasdasdasd asdasdasdas
Pact and PactoConsumer Driven Contract Testing
45
Consumer Movie Catalog Service
Mock Service
asdasdasda asdasdasdas asdasdasd asdasdasda
asdasdasdasd asdasdasdasd asdasdasdas
Pact and PactoConsumer Driven Contract Testing
45
Consumer Movie Catalog Service
Mock Service
We can now use this ‘pact’ to test(drive) the service
implementation
asdasdasda asdasdasdas asdasdasd asdasdasda
asdasdasdasd asdasdasdasd asdasdasdas
Levels of confidence
From BDD to TDD
“How much automated testing? As much as you need, but no more.”
Acceptance testing
Acceptance testing
Acceptance testing
Feature: Adding new movies In order to sell more movies As an online movie seller I want to be able to add movies to the catalog Scenario: Adding movies Given the following movie has just come out | title | director | actors | | Jurassic World | Colin Trevorrow | Chris Pratt, Bryce Dallas Howard | When I add this movie to the catalog Then I should be able to find it in the catalog
Feature: Adding new movies In order to sell more movies As an online movie seller I want to be able to add movies to the catalog Scenario: Adding movies Given the following movie has just come out | title | director | actors | | Jurassic World | Colin Trevorrow | Chris Pratt, Bryce Dallas Howard | When I add this movie to the catalog Then I should be able to find it in the catalog
Feature: Adding new movies In order to sell more movies As an online movie seller I want to be able to add movies to the catalog Scenario: Adding movies Given the following movie has just come out | title | director | actors | | Jurassic World | Colin Trevorrow | Chris Pratt, Bryce Dallas Howard | When I add this movie to the catalog Then I should be able to find it in the catalog
@When("I add this movie to the catalog") public void addMovieToCatalog() { movieId = rest().given().contentType("application/json") .content(newMovie) .post("/movies") .then().statusCode(200) .and().extract().jsonPath().getString("id");}
Feature: Adding new movies In order to sell more movies As an online movie seller I want to be able to add movies to the catalog Scenario: Adding movies Given the following movie has just come out | title | director | actors | | Jurassic World | Colin Trevorrow | Chris Pratt, Bryce Dallas Howard | When I add this movie to the catalog Then I should be able to find it in the catalog
Feature: Adding new movies In order to sell more movies As an online movie seller I want to be able to add movies to the catalog Scenario: Adding movies Given the following movie has just come out | title | director | actors | | Jurassic World | Colin Trevorrow | Chris Pratt, Bryce Dallas Howard | When I add this movie to the catalog Then I should be able to find it in the catalog
Feature: Adding new movies In order to sell more movies As an online movie seller I want to be able to add movies to the catalog Scenario: Adding movies Given the following movie has just come out | title | director | actors | | Jurassic World | Colin Trevorrow | Chris Pratt, Bryce Dallas Howard | When I add this movie to the catalog Then I should be able to find it in the catalog
@Then("I should be able to find it in the catalog") public void shouldBeAbleToFindMovieInCatalog() { rest().given().contentType("application/json") .get("/movies/{movieId}", movieId) .then().statusCode(200) .and().body("title", equalTo(newMovie.getTitle())) .and().body("director", equalTo(newMovie.getDirector()));}
Feature: Adding new movies In order to sell more movies As an online movie seller I want to be able to add movies to the catalog Scenario: Adding movies Given the following movie has just come out | title | director | actors | | Jurassic World | Colin Trevorrow | Chris Pratt, Bryce Dallas Howard | When I add this movie to the catalog Then I should be able to find it in the catalog
@Then("I should be able to find it in the catalog") public void shouldBeAbleToFindMovieInCatalog() { rest().given().contentType("application/json") .get("/movies/{movieId}", movieId) .then().statusCode(200) .and().body("title", equalTo(newMovie.getTitle())) .and().body("director", equalTo(newMovie.getDirector()));}
Rest Assured + Serenity
Feature: Adding new movies In order to sell more movies As an online movie seller I want to be able to add movies to the catalog Scenario: Adding movies Given the following movie has just come out | title | director | actors | | Jurassic World | Colin Trevorrow | Chris Pratt, Bryce Dallas Howard | When I add this movie to the catalog Then I should be able to find it in the catalog
@Then("I should be able to find it in the catalog") public void shouldBeAbleToFindMovieInCatalog() { rest().given().contentType("application/json") .get("/movies/{movieId}", movieId) .then().statusCode(200) .and().body("title", equalTo(newMovie.getTitle())) .and().body("director", equalTo(newMovie.getDirector()));}
Testing REST services with Rest-Assured
Testing REST services with Rest-Assuredget(“/movies/findByTitle/Gladiator") .then().body("title", equalTo("Gladiator"));
Testing REST services with Rest-Assuredget(“/movies/findByTitle/Gladiator") .then().body("title", equalTo("Gladiator"));
Testing REST services with Rest-Assured
when().get(“/movies/findByTitle/Gladiator”) .then().body("nominations.category", hasItems("Best Actor", "Special Effects"));
Testing REST services with Rest-Assured
when().get(“/movies/findByTitle/Gladiator”) .then().body("nominations.category", hasItems("Best Actor", "Special Effects"));
Testing REST services with Rest-Assured
given().contentType("application/json") .content(newMovie) .post("/movies") .then().statusCode(200) .and().extract().jsonPath().getString("id");
Testing REST services with Rest-Assured
given().contentType("application/json") .content(newMovie) .post("/movies") .then().statusCode(200) .and().extract().jsonPath().getString("id");
Posting data
Testing REST services with Rest-Assured
Movie matchingMovie = get("/movies/findByTitle/Gladiator").as(Movie.class);
Testing REST services with Rest-Assured
Movie matchingMovie = get("/movies/findByTitle/Gladiator").as(Movie.class);
Convert responses to Java objects
Testing REST services with Rest-Assuredgiven().param("title","Unknown"). when().get("/movies/search"). then().statusCode(404);
Testing REST services with Rest-Assuredgiven().param("title","Unknown"). when().get("/movies/search"). then().statusCode(404);
Check for errors
Resource-level testing
Resource-level testing
Spock
Resource-level testing
Spock
Resource-level testing
Spock
57
@ContextConfiguration(loader = SpringApplicationContextLoader.class, classes = MovieServiceApplication.class) @WebAppConfiguration@IntegrationTest("server.port:0") class WhenFindingMovies extends Specification { @Autowired MovieRepository movieRepository; @Value('${local.server.port}') int port; def setup() { movieRepository.deleteAll(); RestAssured.port = port; } def "should list all movies"() { given: movieCatalogContains([GLADIATOR, LETTERS_FROM_IWO_JIMA, GRAN_TORINO]) when: def movies = when().get("/movies").as(List) then: movies.collect {movie -> movie.title} == ["Gladiator", "Letters from Iwo Jima", "Gran Torino"] }
57
@ContextConfiguration(loader = SpringApplicationContextLoader.class, classes = MovieServiceApplication.class) @WebAppConfiguration@IntegrationTest("server.port:0") class WhenFindingMovies extends Specification { @Autowired MovieRepository movieRepository; @Value('${local.server.port}') int port; def setup() { movieRepository.deleteAll(); RestAssured.port = port; } def "should list all movies"() { given: movieCatalogContains([GLADIATOR, LETTERS_FROM_IWO_JIMA, GRAN_TORINO]) when: def movies = when().get("/movies").as(List) then: movies.collect {movie -> movie.title} == ["Gladiator", "Letters from Iwo Jima", "Gran Torino"] }
Spring Context
57
@ContextConfiguration(loader = SpringApplicationContextLoader.class, classes = MovieServiceApplication.class) @WebAppConfiguration@IntegrationTest("server.port:0") class WhenFindingMovies extends Specification { @Autowired MovieRepository movieRepository; @Value('${local.server.port}') int port; def setup() { movieRepository.deleteAll(); RestAssured.port = port; } def "should list all movies"() { given: movieCatalogContains([GLADIATOR, LETTERS_FROM_IWO_JIMA, GRAN_TORINO]) when: def movies = when().get("/movies").as(List) then: movies.collect {movie -> movie.title} == ["Gladiator", "Letters from Iwo Jima", "Gran Torino"] }
Spring Integration Tests
57
@ContextConfiguration(loader = SpringApplicationContextLoader.class, classes = MovieServiceApplication.class) @WebAppConfiguration@IntegrationTest("server.port:0") class WhenFindingMovies extends Specification { @Autowired MovieRepository movieRepository; @Value('${local.server.port}') int port; def setup() { movieRepository.deleteAll(); RestAssured.port = port; } def "should list all movies"() { given: movieCatalogContains([GLADIATOR, LETTERS_FROM_IWO_JIMA, GRAN_TORINO]) when: def movies = when().get("/movies").as(List) then: movies.collect {movie -> movie.title} == ["Gladiator", "Letters from Iwo Jima", "Gran Torino"] }
Rest Assured
57
@ContextConfiguration(loader = SpringApplicationContextLoader.class, classes = MovieServiceApplication.class) @WebAppConfiguration@IntegrationTest("server.port:0") class WhenFindingMovies extends Specification { @Autowired MovieRepository movieRepository; @Value('${local.server.port}') int port; def setup() { movieRepository.deleteAll(); RestAssured.port = port; } def "should list all movies"() { given: movieCatalogContains([GLADIATOR, LETTERS_FROM_IWO_JIMA, GRAN_TORINO]) when: def movies = when().get("/movies").as(List) then: movies.collect {movie -> movie.title} == ["Gladiator", "Letters from Iwo Jima", "Gran Torino"] }
Controller-level testing
Controller-level testing
Spock
Controller-level testing
Spock
@ContextConfiguration(loader = SpringApplicationContextLoader.class, classes = MovieServiceApplication.class) @WebAppConfiguration@IntegrationTestclass WhenViewingActorDetailsViaTheController extends Specification { @Autowired ArtistController artistController; def "should retrieve actor details for an artist"() { given: movieCatalogContains([GLADIATOR, LETTERS_FROM_IWO_JIMA, THE_GOOD_THE_BAD_AND_THE_UGLY, GRAN_TORINO]) when: Artist artist = artistController.findArtistDetails("Clint Eastwood") then: artist.name == "Clint Eastwood" and: artist.filmsActedIn.containsAll(THE_GOOD_THE_BAD_AND_THE_UGLY, GRAN_TORINO) and: artist.filmsDirected.containsAll(LETTERS_FROM_IWO_JIMA, GRAN_TORINO) }
@ContextConfiguration(loader = SpringApplicationContextLoader.class, classes = MovieServiceApplication.class) @WebAppConfiguration@IntegrationTestclass WhenViewingActorDetailsViaTheController extends Specification { @Autowired ArtistController artistController; def "should retrieve actor details for an artist"() { given: movieCatalogContains([GLADIATOR, LETTERS_FROM_IWO_JIMA, THE_GOOD_THE_BAD_AND_THE_UGLY, GRAN_TORINO]) when: Artist artist = artistController.findArtistDetails("Clint Eastwood") then: artist.name == "Clint Eastwood" and: artist.filmsActedIn.containsAll(THE_GOOD_THE_BAD_AND_THE_UGLY, GRAN_TORINO) and: artist.filmsDirected.containsAll(LETTERS_FROM_IWO_JIMA, GRAN_TORINO) }
Use a fully wired controller
@ContextConfiguration(loader = SpringApplicationContextLoader.class, classes = MovieServiceApplication.class) @WebAppConfiguration@IntegrationTestclass WhenViewingActorDetailsViaTheController extends Specification { @Autowired ArtistController artistController; def "should retrieve actor details for an artist"() { given: movieCatalogContains([GLADIATOR, LETTERS_FROM_IWO_JIMA, THE_GOOD_THE_BAD_AND_THE_UGLY, GRAN_TORINO]) when: Artist artist = artistController.findArtistDetails("Clint Eastwood") then: artist.name == "Clint Eastwood" and: artist.filmsActedIn.containsAll(THE_GOOD_THE_BAD_AND_THE_UGLY, GRAN_TORINO) and: artist.filmsDirected.containsAll(LETTERS_FROM_IWO_JIMA, GRAN_TORINO) } Use domain objects rather than REST calls
@ContextConfiguration(loader = SpringApplicationContextLoader.class, classes = MovieServiceApplication.class) @WebAppConfiguration@IntegrationTestclass WhenViewingActorDetailsViaTheController extends Specification { @Autowired ArtistController artistController; def "should retrieve actor details for an artist"() { given: movieCatalogContains([GLADIATOR, LETTERS_FROM_IWO_JIMA, THE_GOOD_THE_BAD_AND_THE_UGLY, GRAN_TORINO]) when: Artist artist = artistController.findArtistDetails("Clint Eastwood") then: artist.name == "Clint Eastwood" and: artist.filmsActedIn.containsAll(THE_GOOD_THE_BAD_AND_THE_UGLY, GRAN_TORINO) and: artist.filmsDirected.containsAll(LETTERS_FROM_IWO_JIMA, GRAN_TORINO) }
Service or domain-level testing
Service or domain-level testing
Spock
MovieRepository repositorydef artistServicedef setup() { repository = Mock(MovieRepository) artistService = new ArtistService(movieRepository: repository) } def "should build actor details from films the artist has acted in and has directed"() { given: "Clint Eastwood has directed some films" repository.findByDirector("Clint Eastwood") >> [LETTERS_FROM_IWO_JIMA, GRAN_TORINO] and: "Clint Eastwood has starred in some films" repository.findByActors("Clint Eastwood") >> [THE_GOOD_THE_BAD_AND_THE_UGLY, GRAN_TORINO] when: def artistDetails = artistService.findArtistByName("Clint Eastwood") then: artistDetails.isPresent() and: artistDetails.get().name == "Clint Eastwood" && artistDetails.get().filmsActedIn == [THE_GOOD_THE_BAD_AND_THE_UGLY, GRAN_TORINO] && artistDetails.get().filmsDirected == [LETTERS_FROM_IWO_JIMA, GRAN_TORINO] }
MovieRepository repositorydef artistServicedef setup() { repository = Mock(MovieRepository) artistService = new ArtistService(movieRepository: repository) } def "should build actor details from films the artist has acted in and has directed"() { given: "Clint Eastwood has directed some films" repository.findByDirector("Clint Eastwood") >> [LETTERS_FROM_IWO_JIMA, GRAN_TORINO] and: "Clint Eastwood has starred in some films" repository.findByActors("Clint Eastwood") >> [THE_GOOD_THE_BAD_AND_THE_UGLY, GRAN_TORINO] when: def artistDetails = artistService.findArtistByName("Clint Eastwood") then: artistDetails.isPresent() and: artistDetails.get().name == "Clint Eastwood" && artistDetails.get().filmsActedIn == [THE_GOOD_THE_BAD_AND_THE_UGLY, GRAN_TORINO] && artistDetails.get().filmsDirected == [LETTERS_FROM_IWO_JIMA, GRAN_TORINO] }
Mocked repositories
MovieRepository repositorydef artistServicedef setup() { repository = Mock(MovieRepository) artistService = new ArtistService(movieRepository: repository) } def "should build actor details from films the artist has acted in and has directed"() { given: "Clint Eastwood has directed some films" repository.findByDirector("Clint Eastwood") >> [LETTERS_FROM_IWO_JIMA, GRAN_TORINO] and: "Clint Eastwood has starred in some films" repository.findByActors("Clint Eastwood") >> [THE_GOOD_THE_BAD_AND_THE_UGLY, GRAN_TORINO] when: def artistDetails = artistService.findArtistByName("Clint Eastwood") then: artistDetails.isPresent() and: artistDetails.get().name == "Clint Eastwood" && artistDetails.get().filmsActedIn == [THE_GOOD_THE_BAD_AND_THE_UGLY, GRAN_TORINO] && artistDetails.get().filmsDirected == [LETTERS_FROM_IWO_JIMA, GRAN_TORINO] }
Call the service
MovieRepository repositorydef artistServicedef setup() { repository = Mock(MovieRepository) artistService = new ArtistService(movieRepository: repository) } def "should build actor details from films the artist has acted in and has directed"() { given: "Clint Eastwood has directed some films" repository.findByDirector("Clint Eastwood") >> [LETTERS_FROM_IWO_JIMA, GRAN_TORINO] and: "Clint Eastwood has starred in some films" repository.findByActors("Clint Eastwood") >> [THE_GOOD_THE_BAD_AND_THE_UGLY, GRAN_TORINO] when: def artistDetails = artistService.findArtistByName("Clint Eastwood") then: artistDetails.isPresent() and: artistDetails.get().name == "Clint Eastwood" && artistDetails.get().filmsActedIn == [THE_GOOD_THE_BAD_AND_THE_UGLY, GRAN_TORINO] && artistDetails.get().filmsDirected == [LETTERS_FROM_IWO_JIMA, GRAN_TORINO] }
Check the outcomes
MovieRepository repositorydef artistServicedef setup() { repository = Mock(MovieRepository) artistService = new ArtistService(movieRepository: repository) } def "should build actor details from films the artist has acted in and has directed"() { given: "Clint Eastwood has directed some films" repository.findByDirector("Clint Eastwood") >> [LETTERS_FROM_IWO_JIMA, GRAN_TORINO] and: "Clint Eastwood has starred in some films" repository.findByActors("Clint Eastwood") >> [THE_GOOD_THE_BAD_AND_THE_UGLY, GRAN_TORINO] when: def artistDetails = artistService.findArtistByName("Clint Eastwood") then: artistDetails.isPresent() and: artistDetails.get().name == "Clint Eastwood" && artistDetails.get().filmsActedIn == [THE_GOOD_THE_BAD_AND_THE_UGLY, GRAN_TORINO] && artistDetails.get().filmsDirected == [LETTERS_FROM_IWO_JIMA, GRAN_TORINO] }
Living Documentation
Living Documentation
Living Functional Documentation with Serenity and Cucumber
65
Scenario: Adding movies Given the following movie has just come out | title | director | actors | | Jurassic World | Colin Trevorrow | Chris Pratt, Bryce Dallas Howard | When I add this movie to the catalog Then I should be able to find it in the catalog
Executable Specifications
Automated tests Living documentation
66
66
Context
66
Context
Narrative
67
67
Technical details
Living API Documentation with Swagger
Springfox
69
YAML Specifications Documentation website
Client libraries Test stubs
70
@RequestMapping(method = GET) @ApiOperation("List all the movies in the catalog") public List<Movie> findAll() { return repository.findAll();} @RequestMapping(method = POST) @ApiOperation(value = "Add a new movie to the catalog", httpMethod = "POST") public Movie add(@RequestBody Movie newMovie) { return repository.save(newMovie);} @RequestMapping(value = "/{id}", method = DELETE) @ApiOperation(value = "Removing a movie from the catalog", httpMethod = "DELETE") public void delete(@PathVariable String id) { repository.delete(id);}
Springfox
Some annotations…
Swagger and Spring Boot
70
@RequestMapping(method = GET) @ApiOperation("List all the movies in the catalog") public List<Movie> findAll() { return repository.findAll();} @RequestMapping(method = POST) @ApiOperation(value = "Add a new movie to the catalog", httpMethod = "POST") public Movie add(@RequestBody Movie newMovie) { return repository.save(newMovie);} @RequestMapping(value = "/{id}", method = DELETE) @ApiOperation(value = "Removing a movie from the catalog", httpMethod = "DELETE") public void delete(@PathVariable String id) { repository.delete(id);}
Springfox
Some annotations…
Swagger and Spring Boot
70
@RequestMapping(method = GET) @ApiOperation("List all the movies in the catalog") public List<Movie> findAll() { return repository.findAll();} @RequestMapping(method = POST) @ApiOperation(value = "Add a new movie to the catalog", httpMethod = "POST") public Movie add(@RequestBody Movie newMovie) { return repository.save(newMovie);} @RequestMapping(value = "/{id}", method = DELETE) @ApiOperation(value = "Removing a movie from the catalog", httpMethod = "DELETE") public void delete(@PathVariable String id) { repository.delete(id);}
Springfox
Some annotations…
Swagger and Spring Boot
70
@RequestMapping(method = GET) @ApiOperation("List all the movies in the catalog") public List<Movie> findAll() { return repository.findAll();} @RequestMapping(method = POST) @ApiOperation(value = "Add a new movie to the catalog", httpMethod = "POST") public Movie add(@RequestBody Movie newMovie) { return repository.save(newMovie);} @RequestMapping(value = "/{id}", method = DELETE) @ApiOperation(value = "Removing a movie from the catalog", httpMethod = "DELETE") public void delete(@PathVariable String id) { repository.delete(id);}
Springfox
Some annotations…
Swagger and Spring Boot
71
Springfox
@EnableAutoConfiguration@EnableSwagger2public class MovieServiceApplication { public static void main(String[] args) { SpringApplication.run(MovieServiceApplication.class, args); } @Bean public Docket moviesApi() { return new Docket(DocumentationType.SWAGGER_2) .select() .apis(RequestHandlerSelectors.any()) .paths(PathSelectors.any()) .build() .pathMapping("/") .directModelSubstitute(LocalDate.class, String.class) .genericModelSubstitutes(ResponseEntity.class); }} Some configuration…
Swagger and Spring Boot
72
Springfox
Documentationbundled with your
web services
Swagger and Spring Boot
Production monitoring
Automated MonitoringFast provisioningA fast deployment pipeline
So you want to micro-service?
Automated health checksReal operationsSmoke tests in production
Simple checks may not be enough
An example with Spring Actuator@Componentpublic class CatalogHealth implements HealthIndicator { @Autowired MovieRepository movieRepository; @Override public Health health() { return (isCatalogStocked()) ? Health.status(new Status("UP", "Movie count: " + movieRepository.count())).build() : Health.down().build(); } private boolean isCatalogStocked() { return (movieRepository.count() > 0); }}
An example with Spring Actuator@Componentpublic class CatalogHealth implements HealthIndicator { @Autowired MovieRepository movieRepository; @Override public Health health() { return (isCatalogStocked()) ? Health.status(new Status("UP", "Movie count: " + movieRepository.count())).build() : Health.down().build(); } private boolean isCatalogStocked() { return (movieRepository.count() > 0); }}
A Spring Actuator Health Check
An example with Spring Actuator@Componentpublic class CatalogHealth implements HealthIndicator { @Autowired MovieRepository movieRepository; @Override public Health health() { return (isCatalogStocked()) ? Health.status(new Status("UP", "Movie count: " + movieRepository.count())).build() : Health.down().build(); } private boolean isCatalogStocked() { return (movieRepository.count() > 0); }}
A Spring Actuator Health Check
Custom logic to decide if the app is working
An example with Spring Actuator@Componentpublic class MovieTransactionHealth implements HealthIndicator { @Autowired MovieController movieController; @Override public Health health() { return Health.status(transactionStatus()).build(); } private Status transactionStatus() { try { Movie movie = new Movie("TEST title", "TEST description", "TEST director", ImmutableList.of("TEST actor")); Movie reloadedMovie = movieController.add(movie); movieController.delete(reloadedMovie.getId()); return Status.UP; } catch (Throwable e) { return new Status("DOWN", e.toString()); } }}
An example with Spring Actuator@Componentpublic class MovieTransactionHealth implements HealthIndicator { @Autowired MovieController movieController; @Override public Health health() { return Health.status(transactionStatus()).build(); } private Status transactionStatus() { try { Movie movie = new Movie("TEST title", "TEST description", "TEST director", ImmutableList.of("TEST actor")); Movie reloadedMovie = movieController.add(movie); movieController.delete(reloadedMovie.getId()); return Status.UP; } catch (Throwable e) { return new Status("DOWN", e.toString()); } }}
More complex custom logic
An example with Spring Actuator
@Componentpublic class MovieTransactionHealth implements HealthIndicator { @Autowired MovieController movieController; @Override public Health health() { return Health.status(transactionStatus()).build(); } private Status transactionStatus() {…} }
@Componentpublic class CatalogHealth implements HealthIndicator { @Autowired MovieRepository movieRepository; @Override public Health health() { return (isCatalogStocked()) ? Health.status(new Status("UP", "Movie count: " + movieRepository.count())).build() : Health.down().build(); } private boolean isCatalogStocked() {…} }
Try all this out at home!
Install Gradle
Install MongoDB
Clone the repowakaleo/movie-service