Testing Java Microservices Devoxx be 2017

  • View
    447

  • Download
    11

  • Category

    Software

Preview:

Citation preview

Testing Java MicroservicesFrom Unit to Deployment TestsAlex Soto@alexsotob

Andy Gumbrecht@AndyGeeDe

@alexsotob @AndyGeeDe2

Alex Soto

Red Hat Engineer www.lordofthejars.com@alexsotob

Who Am I?

@alexsotob @AndyGeeDe3

Andy Gumbrecht

Tomitribe Consultant Developerwww.tomitribe.com

Apache TomEE PMCTomee.apache.org

@AndyGeeDeAlso a dad

Who Am I?

@alexsotob @AndyGeeDe4

https://www.manning.com/books/testing-java-microservices

Oops! Shameless plug alert

@alexsotob @AndyGeeDe5

What Are Microservices?

@alexsotob @AndyGeeDe6

Anatomy of Microservice

http://microprofile.io

https://projects.eclipse.org/proposals/eclipse-microprofile

@alexsotob @AndyGeeDe7

Resources

Domain

Controllers

RepositoriesGatew

ays

External Service

@alexsotob @AndyGeeDe8

Testing Evolution

Manual Tests

After Code Automatic

Test

Test First TDD and BDD

Service Virtualization

and CDC

Testing in Production

@alexsotob

Unit

Component

DeploymentIntegration

Contract

What does this mean in terms of testing?Testing Strategies

9

Production

@alexsotob @AndyGeeDe10

@alexsotob @AndyGeeDe11

@alexsotob @AndyGeeDe12

Questions

@alexsotob @AndyGeeDe13

Unit Testing

@alexsotob @AndyGeeDe14

@alexsotob @AndyGeeDe15

Test Doubles

@alexsotob

DummiesPassed but not used

FakesImplementation with shortcuts

StubsProvide predefined answers

MocksPre-programmed expectations,

SpiesRecord information

Test DoublesKind of test doubles

16

@alexsotob @AndyGeeDe

JUnit Test

@Test

public void should_find_composer_by_name() {

// Given:

Composers composers = new Composers();

// When:

final Composer mozart = composers.findComposerByName("Wolfgang Amadeus Mozart");

// Then:

assertThat(mozart).returns("Wolfgang Amadeus Mozart", Composer::getName);

}

17

Readable name

BDD style

Assertions

@alexsotob @AndyGeeDe18

Questions

@alexsotob19

AssertJ

@alexsotob @AndyGeeDe

AssertJ Test

import static org.assertj.core.api.Assertions.assertThat;

@Test

public void should_find_composer_by_name() {

// Given:

Composers composers = new Composers();

// When:

final Composer mozart = composers.findComposerByName("Wolfgang Amadeus Mozart”);

// Then:

assertThat(mozart.getName()).isEqualTo("Wolfgang Amadeus Mozart”);

assertThat(mozart).returns("Wolfgang Amadeus Mozart", Composer::getName);

assertThat(mozart.getBirthdate()).isEqualTo(LocalDate.of(1756, 1, 27));

assertThat(mozart).isEqualToComparingFieldByField(expectedMozart);

assertThat(mozart).isEqualToIgnoringNullFields(expectedMozart);

}

20

Static Import

Readable Assertions

Not Depending on Equals

@alexsotob @AndyGeeDe

AssertJ Test Collections@Test

public void should_find_operas_by_composer_name() {

// Given:

Composers composers = new Composers();

// When:

final List<Opera> operas = composers.findOperasByComposerName("Wolfgang Amadeus Mozart");

// Then:

assertThat(operas)

.hasSize(2)

.extracting(Opera::getName)

.containsExactlyInAnyOrder("Die Zauberflöte", "Don Giovanni”);

}

21

Chained method (IDE support)

Creates a list of getName results

Methods for String

@alexsotob @AndyGeeDe

AssertJ Soft Assertions@Test

public void should_find_composer_by_name_soft_assertions() {

// Given:

Composers composers = new Composers();

// When:

final Composer mozart = composers.findComposerByName("Wolfgang Amadeus Mozart");

// Then:

SoftAssertions.assertSoftly(softly -> {

softly.assertThat(mozart.getName()).isEqualTo("Wolfgang Amadeus Mozart");

softly.assertThat(mozart.getEra()).isEqualTo(Era.CLASSICAL);

softly.assertThat(mozart.getBirthdate()).isEqualTo(LocalDate.of(1756, 1, 27));

softly.assertThat(mozart.getDied()).isEqualTo(LocalDate.of(1791, 12, 5));

});

}

22

Java 8 Lambda

All assertions in Block

@alexsotob @AndyGeeDe

AssertJ Exceptions@Test

public void should_throw_exception_if_composer_not_found_version_3() {

// Given:

Composers composers = new Composers();

// When:

Throwable thrown = catchThrowable(() -> composers.findComposerByName("Antonio Salieri"));

// Then:

assertThat(thrown).isInstanceOf(IllegalArgumentException.class)

.withFailMessage("Composer Antonio Salieri is not found”);

}

23

Catch Exception of Lambda

Assertion Methods for Exceptions

@alexsotob @AndyGeeDe24

Questions

@alexsotob @AndyGeeDe25

@alexsotob @AndyGeeDe

Init Mocks

@RunWith(MockitoJUnitRunner.class)

public class Test {

@Mock EmailService email;

}

public class Test {

@Mock EmailService email;

@Before public void before() {

MockitoAnnotations.initMocks(this)

}

}

@Rule public MockitoRule mockito = MockitoJUnit.rule();

EmailService emailService = Mockito.mock(EmailService.class);

26

Using JUnit Runner

Defines Mock

Init programmatically

Without annotation

With JUnit Rule

@alexsotob @AndyGeeDe

Using Mocks@Mock EmailService email;

Mockito.when(email.receiveBodyMessagesWithSubject("My Subject”))

.thenReturn("This is My Message")

Mockito.when(email.receiveBodyMessagesWithSubject(Matchers.anyString()))

.thenReturn(“This is My Message");

27

Record expectation

Concrete Parameter

Any parameter

@alexsotob @AndyGeeDe

Verifying Mocks

@Mock EmailService email;

InvoiceService invoiceService = new InvoiceService(email);

invoiceService.send();

Mockito.verify(email, Mockito.times(1))

.sendMessage(Matchers.anyString(), Matchers.anyString());

28

Mock used as collaborator

Verify mocked method is called once

@alexsotob @AndyGeeDe

Capturing Arguments Mocks@Mock EmailService email;

InvoiceService invoiceService = new InvoiceService(email);

invoiceService.send();

verify(email, times(1))

.sendMessage(

argThat(subject -> subject.startsWith(“Invoice:")),

Matchers.anyString()

);

29

Mock used as collaborator

Verify content first param value of sendMessage

@alexsotob @AndyGeeDe

Bonus Track (JUnit 5)@ExtendWith(MockitoExtension.class)

class MyMockitoTest {

@BeforeEach

void init(@Mock Person person) {

when(person.getName()).thenReturn("Dilbert");

}

@Test

void simpleTestWithInjectedMock(@Mock Person person) {

assertThat(person.getName()).isEqualTo("Dilbert");

}

}

30

Mockito Extension

Initialize Mock before each test

Use injected Mock

@alexsotob @AndyGeeDe

DEMO

31

@alexsotob @AndyGeeDe32

Questions

@alexsotob @AndyGeeDe33

Component Testing

@alexsotob @AndyGeeDe

Arquillian@RunWith(Arquillian.class)

public class ColorServiceTest {

@Deployment(testable = false)

public static WebArchive createDeployment() {

return ShrinkWrap.create(WebArchive.class).addClasses(ColorService.class, Color.class);

}

@ArquillianResource

private URL webappUrl;

@Test

public void getColorObject() throws Exception {

final WebClient webClient = WebClient.create(webappUrl.toURI());

final Color color =

webClient.accept(MediaType.APPLICATION_JSON).path("color/object").get(Color.class);

}

}

34

Arquillian Runner

WAR Deployment

Injects the application URL

Real REST call

@alexsotob @AndyGeeDe

Spring Boot@RunWith(SpringRunner.class)

@SpringBootTest

public class MyTests {

@Autowired

Invoice invoice;

@Test

public void should_generate_invoice_report() {

}

}

35

Spring Runner

Mock Servlet Runner

Inject Invoice

@alexsotob @AndyGeeDe

Spring Boot REST@RunWith(SpringRunner.class)

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)

public class RandomPortExampleTests {

@Autowired

private TestRestTemplate restTemplate;

@LocalServerPort

int port;

@Test

public void exampleTest() {

String body = this.restTemplate.getForObject("/", String.class);

// assertions

}

}

36

Spring Runner Starts Embedded Servlet Runner

REST calls to running server

Port where servlet is started

Executes REST call

@alexsotob @AndyGeeDe

Wildfly Swarm

@RunWith(Arquillian.class)

@DefaultDeployment

public class InContainerTest {

@Inject

private Invoice invoice;

@Test

public void should_generate_an_invoice() {

}

}

37

Arquillian Runner

Create the deployment of the entire application.

CDI injects Invoice and its deps

@alexsotob @AndyGeeDe

Vert.X@RunWith(VertxUnitRunner.class)

public class CalculatorVerticleTest {

@Rule

public RunTestOnContext rule = new RunTestOnContext();

@Before

public void before(TestContext context) throws IOException {

rule.vertx().deployVerticle(CalculatorVerticle.class.getName(), options,

context.asyncAssertSuccess());

}

@After

public void after(TestContext context) {

rule.vertx().close(context.asyncAssertSuccess());

}

@Test

public void should_add_two_numbers(TestContext context) {}

38

VertX runner

Deploy Verticle under test

Shutdown VertX server

@alexsotob @AndyGeeDe

DEMO

39

https://github.com/tomitribe/tomee-jaxrs-starter-project

@alexsotob @AndyGeeDe40

Questions

@alexsotob41

REST API

@alexsotob @AndyGeeDe42

@alexsotob @AndyGeeDe

REST-assured Example

@Test

public void should_find_composer() {

given()

.when()

.get("{composer}", "Ludwig van Beethoven")

.then()

.assertThat()

.body("name", is("Ludwig van Beethoven"))

.body("operas.size()", is(1))

.body("operas.name", hasItems("Fidelio"));

}

43

GET Http Method with Placeholders

GPath Expression

@alexsotob @AndyGeeDe

REST-assured Request Specification

.get("http://example.com/{composer}", "Ludwig van Beethoven")

RequestSpecBuilder builder = new RequestSpecBuilder();

builder.setBaseUri("http://example.com");

given().spec(builder.build())...

44

Use domain directly

Use Spec Builder

Reuse Everywhere

@alexsotob @AndyGeeDe

REST-assured Auth

given().auth().oauth2(accessToken).when()...

given().auth().form("John", "Doe", springSecurity().withCsrfFieldName("_csrf")).when()...

given().auth().basic("username", "password").when()...

45

@alexsotob @AndyGeeDe

Custom ParsersNot just JSON and XML

SSL Support.relaxedHTTPSValidation()

FiltersInput/Output modification

JSON Schema ValidationContent not Important

More Features

of Rest-Assured

46

@alexsotob @AndyGeeDe

DEMO

47

@alexsotob @AndyGeeDe48

Questions

@alexsotob49

Service Virtualization

@alexsotob

Service Virtualization Capture Mode

50

Service A External Network Service B

Scripts

Proxy

@alexsotob

Service Virtualization Simulate Mode

51

Service A External Network Service B

Scripts

Proxy

@alexsotob @AndyGeeDe52

@alexsotob @AndyGeeDe

Hoverfly Example@ClassRule

public static HoverflyRule hoverflyRule =

HoverflyRule.inCaptureOrSimulationMode("getcomposers.json");

@Test

public void should_get_composers_from_composers_microservice() {

// Given:

ComposersGateway composersGateway = new ComposersGateway("operas.com", 8081);

// When:

Composer composer = composersGateway.getComposer("Ludwig van Beethoven");

// Then:

assertThat(composer.getName()).isEqualTo("Ludwig van Beethoven");

}

53

Start Hoverfly

Use Real Host

Get Data from Real

Proxied data is stored

@alexsotob @AndyGeeDe

Hoverfly Example{

"data" : {

"pairs" : [{

"request" : {

"path" : "/Ludwig van Beethoven",

"method" : "GET",

"destination" : “operas.com:8081",

...

}

"response" : {

"status" : 200,

"body" : "{\"name\":\"Ludwig van Beethoven\",}",

"encodedBody" : false,

"headers" : {

"Connection" : [ "keep-alive" ],

...

}

}

}

}

54

@alexsotob @AndyGeeDe

Simulate Mode

@ClassRule

public static HoverflyRule hoverflyRule =

HoverflyRule.inSimulationMode(classpath("simulation.json"));

SimulationSource.dsl(

service("api.flight.com")

.get("/api/bookings/1")

.willReturn(success("{\"bookingId\":\"1\"\}", "application/json"))

)

55

Hoverfly JUnit Rule

Loads from Hoverfly format Json

Build Request/Responses Programmatically

@alexsotob @AndyGeeDe

Verification

hoverfly.verify(

service(matches("*.flight.*"))

.get("/api/bookings")

.anyQueryParams());

//

hoverfly.verify(

service("api.flight.com")

.put("/api/bookings/1")

.anyBody()

.header("Authorization", "Bearer some-token"), times(2));

56

Verify exactly one request

Verify exactly two requests

@alexsotob @AndyGeeDe

DEMO

57

@alexsotob @AndyGeeDe58

Questions

@alexsotob @AndyGeeDe59

Integration Tests

@alexsotob @AndyGeeDe60

Resources

Domain

Controllers

RepositoriesGatew

ays

External Service

@alexsotob61

Persistence Tests

@alexsotob @AndyGeeDe

First attempt@Test

public void should_find_composer_by_name() {

// Given:

clearDatabase(jdbcUri);

insertComposersData(jdbcUri);

ComposersRepository composersRepository = new ComposersRepository();

// When:

Composer mozart = composersRepository.findComposerByName("Wolfgang Amadeus Mozart");

// Then:

assertThat(mozart).returns("Wolfgang Amadeus Mozart", Composer::getName);

}

62

Prepare Database

Execute Query

@alexsotob63

APE

@alexsotob @AndyGeeDe

APE SQL example@Rule

public ArquillianPersistenceRule arquillianPersistenceRule =

new ArquillianPersistenceRule();

@DbUnit

@ArquillianResource

RdbmsPopulator rdbmsPopulator;

@Before

public void populateData() {

// Given:

rdbmsPopulator.forUri(jdbcUri).withDriver(Driver.class).withUsername("sa")

.withPassword("").usingDataSet("composers.yml")

.execute();

}

64

APE JUnit Rule (not necessary with Arquillian

Runner)

Set DBUnit usage

Configure Connection and Dataset

Populate

@alexsotob @AndyGeeDe

APE SQL example

@After

public void clean_database() {

// Given:

rdbmsPopulator.forUri(jdbcUri).withDriver(Driver.class).withUsername("sa")

.withPassword("").usingDataSet("composers.yml")

.clean();

}

65

Clean After Test

Clean Database

@alexsotob

Boilerplate Code

Programmatic/Declarative@UsingDataSet/@ShouldMatchDataSet

SQL SupportDBUnit and Flyway

REST API SupportPostman Collections

NoSQL SupportMongoDB, Couchbase, CouchDB, Vault, Redis, Infinispan

Benefits of Arquillian APE

66

@alexsotob @AndyGeeDe

DEMO

67

@alexsotob @AndyGeeDe68

Questions

@alexsotob @AndyGeeDe69

@alexsotob @AndyGeeDe

@alexsotob @AndyGeeDe71

@alexsotob @AndyGeeDe

Arquillian Cube DSL

@RunWith(SpringRunner.class)

@SpringBootTest(classes = PingPongController.class, webEnvironment = RANDOM_PORT)

@ContextConfiguration(initializers = PingPongSpringBootTest.Initializer.class)

public class PingPongSpringBootTest {

@ClassRule

public static ContainerDslRule redis = new ContainerDslRule("redis:3.2.6")

.withPortBinding(6379);

@Autowired

TestRestTemplate restTemplate;

@Test

public void should_get_data_from_redis() {

}

72

Spring Boot Test

Custom Initializer

Container DefinitionSets Container Environment

@alexsotob @AndyGeeDe

Arquillian Cube Example@RunWith(Arquillian.class)

public class HelloWorldTest {

@ArquillianResource

@DockerUrl(containerName = "helloworld", exposedPort = 8080)

RequestSpecBuilder requestSpecBuilder;

@Test

public void should_receive_ok_message() {

RestAssured

.given().spec(requestSpecBuilder.build())

.when().get().then()

.assertThat().body("status", equalTo("OK"));

}

}

73

Arquillian Runner

REST-Assured Integration Environment Resolver

Normal REST-Assured Call

src/test/docker/docker-compose.yml

@alexsotob @AndyGeeDe

DEMO

74

@alexsotob @AndyGeeDe75

Questions

@alexsotob @AndyGeeDe76

Micro Services architecture

@alexsotob77

Villains Database Application

Consumer V1 Producer V1GET /crimes/Gru

[{ "name": "Moon", "villain": "Gru", "wiki": "/wiki/Moon"}]

GET /villains/Gru

"name": “Gru”,"areaOfInfluence": "World""crimes": [{ "name": "Moon", "wiki": "/wiki/Moon" }]

X

XV1.1

@alexsotob @AndyGeeDe78

Contract Tests

@alexsotob79

Consumer Side

Stub Server

ConsumerTest

Expectations

GET /crimes/Gru

[{"name":...]}

Sharing

Provider Side

Sharing

Test

Provider

GET /crimes/Gru

[{"name":...]}

@alexsotob81

> Pact FoundationPact specification v3

> Integration with several languagesJVM, Ruby, Python, Go, .Net, Swift, JS

> Pact BrokerSharing contracts, API documentation, Overview of services

> Arquillian AlgeronArquillian ecosystem + Pact, Publishers/Retrievers, JBoss Forge

@alexsotob @AndyGeeDe

Consumer Side@RunWith(Arquillian.class)

public class CrimesConsumerContractTest {

@StubServer

URL pactServer;

@Pact(provider = "crimes", consumer = "villains")

public RequestResponsePact returnListOfCrimes(PactDslWithProvider builder) {

return builder

.uponReceiving("Gru villain to get all Crimes")

.path("/crimes/Gru").method("GET").willRespondWith()

.status(200).body(RESPONSE)

.toPact();

}

@Test

@PactVerification(“crimes”)

public void should_get_list_of_crimes_by_villain() {}

82

Arquillian Runner

Location of Stub Server

Record expectations between C/P

Test execution and contract creation

@alexsotob @AndyGeeDe

Provider Side

@RunWith(Arquillian.class)

@Provider("crimes")

@ContractsFolder("~/crimescontract")

public class CrimesContractTest {

@ArquillianResource

Target target;

@Test

public void should_validate_contract() {

assertThat(target).withUrl(getCrimesServer()).satisfiesContract();

}

}

83

Arquillian Runner

Location of Contract

Http client player

Verification of real responses against contract

@alexsotob84

DEMO

@alexsotob @AndyGeeDe85

Questions

@alexsotob86

Deployment Tests

@alexsotob @AndyGeeDe87

@alexsotob @AndyGeeDe

Deployment Test@RunWith(ArquillianConditionalRunner.class)

@RequiresKubernetes

public class HelloWorldIT {

@ArquillianResource

@RouteURL("kubernetes-hello-world")

URL url;

@Test

public void service_should_be_accessible() throws IOException {

OkHttpClient client = new OkHttpClient();

Request request = new Request.Builder().get().url(url).build();

Response response = client.newCall(request).execute();

assertThat(response.isSuccessful()).isTrue();

}

}

88

Arquillian Runner

Precondition in test

URL for access to service

Verifies connection is possible

@alexsotob89

DEMO

@alexsotob @AndyGeeDe90

Questions

@alexsotob91

Continuous Delivery

@alexsotob92

Smart Testing

@alexsotob93

Production Sources Tests

https://martinfowler.com/articles/rise-test-impact-analysis.html

@alexsotob

Smart Testing Maven Extension

curl -sSL https://git.io/v5jy6 | bash

94

Installs extension

mvn clean test -Dsmart.testing="new, changed, affected"

Runs only new, changed and prod related tests

mvn clean test -Dsmart.testing.mode=ordering -Dsmart.testing="new, changed, affected”

Prioritize new, changed and prod related tests

@alexsotob @AndyGeeDe95

We are on Production

@alexsotob @AndyGeeDe96

Shit! We break Production Environment

@alexsotob @AndyGeeDe97

Blue-Green Deployments

Starts with a “git commit and git push”

Blue/Green Deployment

DEVELOPMENT QA STAGING PRODUCTION ROUTER USERS

BUILDSCM

CLUSTER

Blue/Green Deployment

DEVELOPMENT QA STAGING PRODUCTION ROUTER USERS

BUILDSCM

CLUSTER

Blue/Green Deployment

DEVELOPMENT QA STAGING PRODUCTION ROUTER USERS

BUILDSCM

CLUSTER

Blue/Green Deployment

DEVELOPMENT QA STAGING PRODUCTION ROUTER USERS

BUILDSCM

CLUSTER

Blue/Green Deployment

DEVELOPMENT QA STAGING PRODUCTION ROUTER USERS

BUILDSCM

CLUSTER

Blue/Green Deployment

DEVELOPMENT QA STAGING PRODUCTION ROUTER USERS

SCM

CLUSTER

Blue/Green Deployment

DEVELOPMENT QA STAGING PRODUCTION ROUTER USERS

SCM

CLUSTER

@alexsotob @AndyGeeDe105

Questions

@alexsotob106

Canary Release

Canary Deployment

DEVELOPMENT QA STAGING PRODUCTION ROUTER USERS

SCM

SERVICE

Canary Deployment

DEVELOPMENT QA STAGING PRODUCTION ROUTER USERS

SCM

SERVICE

Canary Deployment

DEVELOPMENT QA STAGING PRODUCTION ROUTER USERS

SCM

SERVICE

Canary Deployment

DEVELOPMENT QA STAGING PRODUCTION ROUTER USERS

SCM

SERVICE

Canary Deployment

DEVELOPMENT QA STAGING PRODUCTION ROUTER USERS

SCM

SERVICE

Canary Deployment

DEVELOPMENT QA STAGING PRODUCTION ROUTER USERS

SCM

SERVICE

Canary Deployment

DEVELOPMENT QA STAGING PRODUCTION ROUTER USERS

SCM

SERVICE

Canary Deployment

DEVELOPMENT QA STAGING PRODUCTION ROUTER USERS

SCM

SERVICE

Canary Deployment

DEVELOPMENT QA STAGING PRODUCTION ROUTER USERS

SCM

SERVICE

Canary Deployment

DEVELOPMENT QA STAGING PRODUCTION ROUTER USERS

SCM

SERVICE

@alexsotob @AndyGeeDe117

Questions

@alexsotob @AndyGeeDe118

Dark Launches

Canary Deployment

DEVELOPMENT QA STAGING PRODUCTION ROUTER USERS

SCM

INTERNAL USERSSERVICE

Canary Deployment

DEVELOPMENT QA STAGING PRODUCTION ROUTER USERS

SCM

SERVICE

@alexsotob121

DEMO

@alexsotob @AndyGeeDe122

Lessons Learnt

@alexsotob

Unit

Component

DeploymentIntegration

Contract

What does it means in terms of Tests?Testing Strategies

123

Production

@alexsotob @AndyGeeDe124

Tests are a Team

@alexsotob @AndyGeeDe125

Secure Your Steps

@alexsotob @AndyGeeDe126

Automate Everything

@alexsotob @AndyGeeDe127

“Change is the essential process of all of existence.”

—Spock

@alexsotob @AndyGeeDe128

Questions

@alexsotob @AndyGeeDe

Useful Linkshttps://github.com/lordofthejars/composers-unicorn

https://github.com/lordofthejars/musicboxhttps://github.com/arquillian-testing-microservices/villains-service/tree/contract_testinghttps://github.com/arquillian-testing-microservices/crimes-service/tree/contract_testinghttps://github.com/arquillian/arquillian-extension-persistence/tree/2.0.0/arquillian-ape-nosqlhttps://github.com/lordofthejars/rest-springboot-openshift